Linux Multi-threading and synchronization
Vamei Source: Http://www.cnblogs.com/vamei Welcome reprint, Please also keep this statement. Thank you!
A typical UNIX system supports one process to create multiple threads (thread). As mentioned in the Linux Process Foundation, Linux organizes operations on a per-process basis, and threads in Linux are also based on processes. Although the implementation is different from other Unix systems, there is no difference in the logic and use of Linux multithreading with real multithreading.
Multithreading
Let's take a look at what multithreading is. In Linux from the program to the process, we see a program in-memory representation. The entire running process of this program, only one control right exists. When the function is called, the function gets control, becomes the active function, and then runs the instruction in the function. At the same time, other functions are out of the field and do not run. As shown in the following:
Linux from program to process
We see that each block is connected by an arrow. Each function is like a line, and the computer executes the actions defined in each function as if it were a pipeline. Such a program is called a single threaded procedure.
Multithreading is allowing multiple controls within a process to be active at the same time, allowing multiple functions to operate simultaneously. Even a single-CPU computer can have the effect of running multiple threads concurrently by switching between instructions on different threads. As shown, is a multithreaded process:
Main () to FUNC3 () and to main () make up a thread, and func1 () and Func2 () make up another two threads. The operating system generally has some system tuning to let you run a function as a new thread.
Recall the functionality and purpose of the stacks we mentioned in Linux from program to process. A stack where only the bottom frame can be read and written. Accordingly, only the function that corresponds to that frame is activated and is in the working state. In order to implement multi-threading, we have to bypass the stack limit. To do this, we build a new stack for this thread when we create a new thread. Each stack corresponds to a thread. When a stack executes to the full popup, the corresponding thread completes the task and is finished. Therefore, the multithreaded process has multiple stacks in memory . Multiple stacks are separated by a certain amount of white space for the stack to grow. Each thread can invoke parameters and variables in the frame at the bottom of its own stack and share the TEXT,HEAP and global data regions in memory with other threads. For the above example, we need to have 3 stacks in our process space.
(Note that for multithreading, because there are multiple stacks in the same process space, any empty space fills up and causes the stack overflow to be a problem.) )
Concurrency
Multithreading is equivalent to a concurrent (concunrrency) system. Concurrent systems typically perform multiple tasks at the same time. If multiple tasks can share resources, especially when writing a variable at the same time, you need to resolve the synchronization problem. For example, we have a multi-threaded train ticketing system that stores the remaining number of votes with the global variable I. Multiple threads are constantly selling tickets (i = i-1) until the remaining number of votes is 0. So each of them needs to do the following:
/*MU is a global mutex*/
while (/*Infinite loop0) i = i-else {printf ("No moreTickets"); Exit (); }}
There is no problem if only one thread executes the above program (the equivalent of a window ticket). But if more than one thread executes the above program (the equivalent of multiple window tickets), we have a problem. We will see that the root cause is that each thread that occurs at the same time can read and write to I.
Our if structure here will give the CPU two instructions, one is to determine whether there are remaining tickets (i! = 0), and one is to sell tickets (i = i-1). A thread will first determine if there is a ticket (for example, I is 1), but there is a time window between the two instructions, the other thread may execute the ticket operation (i = i-1) within this time window, causing the condition of the thread to sell the ticket no longer valid. However, since the thread had already executed the judgment instruction, it was not known that I had changed, so I continued to execute the order to sell the ticket that did not exist (I became a negative number). For a real ticketing system, this would be a serious mistake (too many tickets sold and trains full).
In the case of concurrency, the Order of command execution is determined by the kernel. Within the same thread, instructions are executed sequentially, but instructions between different threads are difficult to clear which one executes first. If the result of the run depends on the execution of different threads, then it will create a competitive condition (race condition), in which case the results of the computer are difficult to predict. We should try to avoid the formation of competitive conditions. The most common way to resolve a race condition is to make the previously separated two instructions a non-separable atomic operation (Atomic operation), while other tasks cannot be inserted into an atomic operation.
multi-threaded synchronization
For multithreaded threads, synchronization (synchronization) means that only one thread is allowed to access a resource for a certain amount of time. During this time, no other threads are allowed to access the resource. We can synchronize resources through mutexes (mutexes), conditional variables (condition variable) and read-write locks (reader-writer lock).
1) Mutual exclusion lock
A mutex is a special variable that has a lock (Lock) and an open (unlock) two states. Mutexes are generally set as global variables. An open mutex can be obtained by a thread. Once obtained, the mutex is locked and only then the thread has permission to open it. Other threads that want to obtain a mutex will wait until the mutex is opened again. We can think of the mutex as a toilet that can hold only one person, and when someone enters the bathroom, they can lock the bathroom from inside. The other person can only wait outside the mutex for the person to come out and go in. People waiting outside the queue, who first saw the bathroom empty, you can first rushed in.
The above problem is easy to solve with the mutex problem, and each thread's program can be changed to:
/*Mu is a global mutex*/
while (1) {/*infinite loop*/ mutex_ Lock (MU); /*aquire Mutex and lock it, if cannot, Wait until mutex is Unblocked*/if (i! = 0) i = i-1else" no more Tickets "); Exit (); } mutex_unlock (MU); /*release mutex, make it Unblocked*/
The first thread executing mutex_lock () gets MU first. Other threads that want to get MU must wait until the first thread executes to Mutex_unlock () to release mu before it can get MU and continue executing the thread. Therefore, when the thread is operating between Mutex_lock () and Mutex_unlock (), it is not affected by other threads, which constitutes an atomic operation.
It is important to note that if a thread is still using the original program (i.e. without attempting to get MU and modifying I directly), the mutex cannot prevent the program from modifying I, and the mutex loses the meaning of protecting the resource. Therefore, the mutex mechanism needs the programmer to write the perfect program to implement the mutual exclusion lock function. The same is true of the other mechanisms we are talking about here.
2) Condition variable
A condition variable is another common variable. It is also often saved as a global variable and is co-operative with the mutex.
Suppose such a condition: There are 100 workers, each of whom is responsible for decorating a room. When 10 rooms were renovated, the boss informed the 10 workers to go for a beer.
How do we make it work? The boss asked the workers to check the number of rooms that had been renovated after they had finished decorating the room. However, under multi-threading conditions, there is a danger of competitive conditions. In other words, other workers may be able to complete the work between the worker's house and the inspection. Resolved in the following way:
/*Mu:global Mutex, cond:global codition variable, Num:global int*/
Mutex_lock (mu) num = num +1;/*Worker Build the*/if (Num <=10) {/*Worker is within the first ten to finish*/ Cond_wait (MU, cond); wait*/ printf ( "drink beer ");} else if (num = 11) {/*workder is the 11th to Finish*/ Cond_broadcast (MU, cond); /*inform the other 9 to wake up< Span style= "color: #008000;" >*/}mutex_unlock (MU);
The condition variable is used above. In addition to the mutex mate, the condition variable needs to mate with another global variable (num here, which is the number of rooms renovated). This global variable is used to make up each condition.
Specific ideas are as follows. After we have the workers decorate the room (num = num + 1), check the number of rooms that have been renovated (num < 10). Since MU is locked, there will be no other workers to decorate the room during this period (change the value of num). If the worker is the first ten people to complete, then we call the Cond_wait () function.
Cond_wait () does two things, one is to release mu, so that other workers can build houses. The other is to wait until the cond notice. In this case, the eligible thread begins to wait.
When the notice (the tenth room has been built) arrives, Condwait () will lock Mu again. The thread resumes running and executes the next sentence Prinft ("Drink Beer") (Drink beer!). )。 From here, until Mutex_unlock (), it forms another mutex structure.
So, how do the previous 10 threads calling Cond_wait () Get notified? We notice that elif if, the person who built the 11th room, is responsible for calling Cond_broadcast (). This function will broadcast notifications to all threads that call cond_wait () in order for those threads to resume running.
The condition variable is especially useful for multiple threads waiting for a condition to occur. If you do not use a condition variable, then each thread will need to constantly try to obtain a mutex and check whether the condition occurs, which wastes the system's resources considerably.
3) read/write lock
Read-write locks are very similar to mutex locks. R, RW Lock has three states: shared read lock (Shared-read), Mutex write lock (Exclusive-write lock), open (unlock). The last two states are exactly the same as the two previous mutexes.
A unlock RW lock can be used by a thread to acquire an R lock or a W lock.
If an R lock is obtained by a thread, RW lock can continue to acquire the R lock by another thread without having to wait for the thread to release the R lock. However, if another thread wants to obtain a w lock at this point, it must wait until all the threads holding the shared read lock release the respective R locks.
If a lock is obtained by a thread with a W lock, then other threads, whether they want to acquire an R lock or a W lock, must wait for the thread to release the W lock.
This allows multiple threads to read shared resources at the same time. And the risky write operation is protected by mutual exclusion lock.
We need to synchronize the concurrency system, which makes programming difficult for programmers. However, multi-threaded system can solve many io bottleneck problems very well. For example, we listen to network ports. If we have only one thread, then we must listen, receive requests, process, reply, and listen again. If we use multithreaded systems, we can have multiple threads listening. When one of our threads is processing, we can also have other threads continue to listen, thus greatly improving the utilization of the system. In the increasingly large data, server read and write operations more and more today, this has considerable significance. Multithreading can also make more efficient use of multi-CPU environments.
(like cooking, keep switching to different dishes.) )
The program in this article uses pseudo C notation. Different languages have different function names (such as Mutex_lock). The focus here is on logical concepts, not specific implementations and language specifications.
Summary
Multiple threads, multiple stacks
Race condition
Mutex, condition variable, RW lock
Welcome to the Linux concept and System series article
Linux multi-Threading and synchronization