Thread Data Processing
Compared with a process, one of the biggest advantages of a thread is data sharing. Each process shares the data segment that follows the parent process to conveniently obtain and modify data. But this also brings many problems to multithreaded programming. We must be careful that there are multiple different processes accessing the same variable. Many functions cannot be reentrant, that is, they cannot run multiple copies of a function at the same time (unless different data segments are used ). Static variables declared in functions often cause problems and return values of functions. If the returned address is the address of the Space statically declared by the function, when a thread calls the function to obtain the address and uses the data pointed to by the address, other threads may call this function and modify this data segment. Shared variables must be defined with the keyword volatile in the process to prevent the compiler from changing their usage methods during optimization (for example, using the-ox parameter in GCC. To protect variables, we must use semaphores, mutex, and other methods to ensure correct use of variables. Next, we will gradually introduce the relevant knowledge when processing thread data.
1. Thread data
In a single-threaded program, there are two basic types of data: global variables and local variables. However, in multi-threaded programs, there is also a third data type: thread data (TSD: thread-specific data ). It is similar to a global variable. In a thread, each function can call it like a global variable, but it is invisible to other threads outside the thread. The necessity of such data is obvious. For example, the common variable errno returns standard error information. Obviously, it cannot be a local variable. Almost every function can call it, but it cannot be a global variable, otherwise, the error message of line B may be output in line. To implement such variables, we must use thread data. We create a key for each thread data, which is associated with this key. In each thread, this key is used to represent the thread data, but in different threads, this key represents different data. In the same thread, it represents the same data content.
There are four main functions related to thread data: Create a key, specify thread data for a key, read thread data from a key, and delete a key.
The function prototype for creating a key is:
Extern int pthread_key_create _ p (pthread_key_t * _ key, void (* _ destr_function) (void *)));
The first parameter is a pointer to a key value, and the second parameter specifies a destructor function. If this parameter is not null, when each thread ends, the system will call this function to release the memory block bound to this key. This function is often used with the function pthread_once (pthread_once_t * once_control, void (* initroutine) (void) to create this key only once. The pthread_once function declares an initialization function. When pthread_once is called for the first time, it executes this function and will be ignored in future calls.
In the following example, we create a key and associate it with a certain data. We need to define a function createwindow, which defines a graphical window (the data type is fl_window *, which is the data type in the graphic interface development tool fltk ). Since each thread calls this function, we use thread data.
/* Declare a key */<br/> pthread_key_t mywinkey; <br/>/* function createwindow */<br/> void createwindow (void) {<br/> fl_window * Win; <br/> static pthread_once_t once = pthread_once_init; <br/>/* call the createmykey function, create key */<br/> pthread_once (& once, createmykey ); <br/>/* win points to a new window */<br/> win = new fl_window (0, 0,100,100, "mywindow "); <br/>/* Make some possible settings for this window, such as the size, position, and name */<br/> setwindow (WIN ); <br/>/* bind the window pointer value to the key mywinkey */<br/> pthread_setpecific (mywinkey, Win ); <br/>}</P> <p>/* function createmykey, creates a key, and specifies the Destructor */<br/> void createmykey (void) {<br/> pthread_keycreate (& mywinkey, freewinkey); <br/>}</P> <p>/* function freewinkey, release space */<br/> void freewinkey (fl_window * Win) {<br/> Delete win; <br/>}
In this way, the createmywin function can be called in different threads to obtain window variables that can be seen inside the thread. This variable is obtained through the pthread_getspecific function. In the above example, we have used the pthread_setspecific function to bind thread data with a key. The two functions are prototype as follows:
Extern int pthread_setspecific _ p (pthread_key_t _ key ,__ const void * _ pointer )); <br/> extern void * pthread_getspecific _ p (pthread_key_t _ key ));
The parameter meanings and usage of these two functions are obvious. Note that when pthread_setspecpacific is used as a key to specify new thread data, the original thread data must be released to recycle space. This process function pthread_key_delete is used to delete a key. The memory occupied by this key will be released, but note that it only releases the memory occupied by the key, does not release the memory resources occupied by the thread data associated with the key, and it does not trigger the destructor function defined in the pthread_key_create function. The release of thread data must be completed before the release key.
2. mutex lock
Mutex lock is used to ensure that only one thread is executing a piece of code within a period of time. Necessity is obvious: assuming that each thread writes data to the same file in sequence, the final result must be disastrous.
Let's take a look at the following code. This is a read/write program. They share a buffer zone, and we assume that a buffer zone can only save one piece of information. That is, the buffer has only two States: information or no information.
Void reader_function (void); <br/> void writer_function (void); </P> <p> char buffer; <br/> int buffer_has_item = 0; <br/> pthread_mutex_t mutex; <br/> struct timespec delay; <br/> void main (void) {<br/> pthread_t reader; <br/>/* define the delay time */<br/> delay. TV _sec = 2; <br/> delay. TV _nec = 0; <br/>/* use the default attribute to initialize a mutex lock object */<br/> pthread_mutex_init (& mutex, null ); <br/> pthread_create (& reader, pthread_attr_default, (void *) & reader_function), null); <br/> writer_function (); <br/>}</P> <p> void writer_function (void) {<br/> while (1) {<br/>/* Lock mutex lock */<br/> pthread_mutex_lock (& mutex); <br/> If (buffer_has_item = 0) {<br/> buffer = make_new_item (); <br/> buffer_has_item = 1; <br/>}< br/>/* Open the mutex lock */<br/> pthread_mutex_unlock (& mutex); <br/> pthread_delay_np (& delay ); <br/>}</P> <p> void reader_function (void) {<br/> while (1) {<br/> pthread_mutex_lock (& mutex); <br/> If (buffer_has_item = 1) {<br/> consume_item (buffer); <br/> buffer_has_item = 0; <br/>}< br/> pthread_mutex_unlock (& mutex); <br/> pthread_delay_np (& delay); <br/>}< br/>}
The mutex variable mutex is declared here. The structure pthread_mutex_t is an undisclosed data type, which contains an attribute object allocated by the system. The pthread_mutex_init function is used to generate a mutex lock. The null parameter indicates that the default attribute is used. To declare a mutex lock for a specific attribute, call the pthread_mutexattr_init function. The pthread_mutexattr_setpshared function and the pthread_mutexattr_settype function are used to set the mutex lock attribute. The previous function sets the property pshared, which has two values: pthread_process_private and pthread_process_shared. The former is used to synchronize threads in different processes, and the latter is used to synchronize different threads in the process. In the preceding example, we use the default pthread_process _ private attribute. The latter is used to set mutex lock types. Optional types include pthread_mutex_normal, pthread_mutex_errorcheck, pthread_mutex_recursive, and pthread _ mutex_default. They define different on-board and unlock mechanisms. Generally, the last default attribute is used.
The pthread_mutex_lock statement starts to lock with mutex lock. Subsequent code is locked until pthread_mutex_unlock is called, that is, only one thread can call and execute the lock at a time. When a thread executes at pthread_mutex_lock, if the lock is used by another thread at this time, the thread is blocked, that is, the program will wait for another thread to release the mutex lock. In the above example, we used the pthread_delay_np function to sleep the thread for a period of time to prevent a thread from occupying this function.
The above example is very simple and I will not introduce it any more. It is suggested that a deadlock may occur when mutex lock is used: two threads try to occupy two resources at the same time, and lock the corresponding mutex lock in different order. For example, both threads need to lock mutex lock 1 and mutex lock 2. Thread a First locks mutex lock 1, line B First locks mutex 2, and a deadlock occurs. In this case, we can use the function pthread_mutex_trylock, which is a non-blocking version of the function pthread_mutex_lock. When it finds that a deadlock is inevitable, it will return the corresponding information, and the programmer can handle the deadlock accordingly. In addition, different mutex lock types have different deadlocks, but the most important thing is that programmers should pay attention to this in programming.
3. Condition Variables
The previous section describes how to use mutex to share and communicate data between threads. One obvious drawback of mutex is that it has only two States: Lock and non-lock. Conditional variables make up for the lack of mutex lock by allowing the thread to block and wait for another thread to send signals. They are often used together with mutex locks. When a condition variable is used to block a thread, when the condition is not met, the thread often unlocks the corresponding mutex and waits for the condition to change. Once another thread changes the condition variable, it will notify the corresponding condition variable to wake up one or more threads that are blocked by this condition variable. These threads will re-lock the mutex and re-test whether the conditions are met. In general, condition variables are used for line-to-line synchronization.
The condition variable structure is pthread_cond_t. The pthread_cond_init () function is used to initialize a condition variable. Its prototype is:
Extern int pthread_cond_init _ p (pthread_cond_t * _ cond ,__ const pthread_condattr_t * _ cond_attr ));
Cond is a pointer to the structure pthread_cond_t, and cond_attr is a pointer to the structure pthread_condattr_t. The structure pthread_condattr_t is the attribute structure of the condition variable. Like the mutex lock, we can use it to set whether the condition variable is available in the process or between processes. The default value is pthread _ process_private, this condition variable is used by various threads in the same process. Note that the initialization condition variables can be reinitialized or released only when they are not used. The function for releasing a condition variable is pthread_cond _ destroy (pthread_cond_t Cond ).
The pthread_cond_wait () function blocks the thread on a condition variable. Its function prototype is:
Extern int pthread_cond_wait _ p (pthread_cond_t * _ cond, pthread_mutex_t * _ mutex ));
The thread unlocks the lock pointed to by mutex and is blocked by the condition variable cond. The thread can be awakened by the pthread_cond_signal function and the pthread_cond_broadcast function. However, it must be noted that the condition variable only blocks and wakes up the thread. the user must provide the specific judgment conditions, for example, whether the value of a variable is 0 is shown in the following example. After the thread is awakened, it will re-check whether the conditions are met. If the conditions are not met, the thread should still be blocked here and be waiting for the next wake-up. This process is generally implemented using the while statement.
Another function used to block threads is pthread_cond_timedwait (). Its prototype is:
Extern int pthread_cond_timedwait _ p (pthread_cond_t * _ cond, pthread_mutex_t * _ mutex, _ const struct timespec * _ abstime ));
It has one more time parameter than the pthread_cond_wait () function. After a period of time in abstime, blocking is also removed even if the condition variable is not met.
The prototype of the function pthread_cond_signal () is:
Extern int pthread_cond_signal _ p (pthread_cond_t * _ Cond ));
It is used to release a thread that is blocked on the condition variable cond. When multiple threads are blocked on this condition variable, which thread is awakened is determined by the thread's scheduling policy. Note that the mutex lock of the Protection Condition variable must be used to protect this function. Otherwise, the signal meeting the condition may be sent between the test condition and the call of the pthread_cond_wait function, this causes unlimited waiting. The following is a simple example of using the functions pthread_cond_wait () and pthread_cond_signal.
Pthread_mutex_t count_lock; <br/> pthread_cond_t count_nonzero; <br/> unsigned count; <br/> decrement_count () {<br/> pthread_mutex_lock (& count_lock ); <br/> while (COUNT = 0) <br/> pthread_cond_wait (& count_nonzero, & count_lock); <br/> COUNT = count-1; <br/> pthread_mutex_unlock (& count_lock); <br/>}</P> <p> increment_count () {<br/> pthread_mutex_lock (& count_lock ); <br/> If (COUNT = 0) <br/> pthread_cond_signal (& count_nonzero); <br/> COUNT = count + 1; <br/> pthread_mutex_unlock (& count_lock); <br/>}
When the Count value is 0, the decrement function is blocked at pthread_cond_wait and the mutual exclusion lock count_lock is enabled. At this time, when the increment_count function is called, The pthread_cond_signal () function changes the condition variable and informs decrement_count () to stop blocking. Readers can try to let the two threads run the two functions separately to see what results will appear.
The pthread_cond_broadcast (pthread_cond_t * Cond) function is used to wake up all threads blocked on the condition variable cond. After these threads are awakened, they will compete for the corresponding mutex lock again. Therefore, you must use this function with caution.
4. semaphores
Semaphores are essentially non-negative integer counters used to control access to public resources. When public resources increase, the sem_post () function is called to increase the semaphore. Public resources can be used only when the signal value is greater than 0. after use, the sem_wait () function reduces semaphores. The sem_trywait () function plays the same role as the pthread _ mutex_trylock () function. It is a non-blocking version of The sem_wait () function. Next we will introduce some functions related to semaphores one by one, which are defined in the header file/usr/include/semaphore. h.
The data type of the semaphore is sem_t, which is essentially a long integer. The sem_init () function is used to initialize a semaphore. Its prototype is:
Extern int sem_init _ p (sem_t * _ SEM, int _ pshared, unsigned int _ value ));
SEM is a pointer to the semaphore structure. If pshared is not 0, the semaphore is shared among processes. Otherwise, it can only be shared among all threads of the current process. value indicates the initial value of the semaphore.
The sem_post (sem_t * SEM) function is used to increase the semaphore value. When a thread is blocked on this semaphore, calling this function will make one of the threads not blocked. The selection mechanism is also determined by the thread scheduling policy.
The sem_wait (sem_t * SEM) function is used to block the current thread until the semaphores SEM value is greater than 0. After blocking is removed, the SEM value is reduced by one, indicating that the public resources are reduced after use. The sem_trywait (sem_t * SEM) function is a non-blocking version of The sem_wait () function, which directly reduces the semaphores SEM value by one.
The sem_destroy (sem_t * SEM) function is used to release semaphores.
Here is an example of using semaphores. In this example, there are a total of four threads, two of which are responsible for reading data from the file to the public buffer, the other two threads read data from the buffer for different processing (addition and multiplication ).
/* File SEM. C */<br/> # include <stdio. h> <br/> # include <pthread. h> <br/> # include <semaphore. h> <br/> # define maxstack 100 <br/> int stack [maxstack] [2]; <br/> int size = 0; <br/> sem_t SEM; <br/>/* from File 1. dat reads data. Once each read, the semaphore is incremented by */<br/> void readdata1 (void) {<br/> file * fp = fopen ("1.dat"," R "); <br/> while (! Feof (FP) {<br/> fscanf (FP, "% d", & stack [size] [0], & stack [size] [1]); <br/> sem_post (& SEM); <br/> + size; <br/>}< br/> fclose (FP ); <br/>}< br/>/* file 2. dat reads data */<br/> void readdata2 (void) {<br/> file * fp = fopen ("2.dat"," R "); <br/> while (! Feof (FP) {<br/> fscanf (FP, "% d", & stack [size] [0], & stack [size] [1]); <br/> sem_post (& SEM); <br/> + size; <br/>}< br/> fclose (FP ); <br/>}< br/>/* block wait for the buffer to have data. After reading the data, release the space and continue waiting */<br/> void handledata1 (void) {<br/> while (1) {<br/> sem_wait (& SEM); <br/> printf ("plus: % d + % d = % DN ", stack [size] [0], stack [size] [1], <br/> stack [size] [0] + stack [size] [1]); <br/> -- size; <br/>}</P> <p> void handledata2 (void) {<br/> while (1) {<br/> sem_wait (& SEM); <br/> printf ("Multiply: % d * % d = % DN", stack [size] [0], stack [size] [1], <br/> stack [size] [0] * stack [size] [1]); <br/> -- size; <br/>}< br/> int main (void) {<br/> pthread_t T1, T2, T3, T4; <br/> sem_init (& SEM, 0, 0); <br/> pthread_create (& T1, null, (void *) handledata1, null ); <br/> pthread_create (& T2, null, (void *) handledata2, null); <br/> pthread_create (& T3, null, (void *) readdata1, null ); <br/> pthread_create (& T4, null, (void *) readdata2, null); <br/>/* prevents premature program exit, make it wait for the */<br/> pthread_join (T1, null); <br/>}</P> <p>
In Linux, run the GCC-lpthread Sem. C-o SEM command to generate the executable file SEM. We have edited the data file in advance. dat and 2.dat. assume that their content is 1 2 3 4 5 6 7 8 9 10 and-1-2-3-4-5-6-7-8-9-10, respectively., run SEM to obtain the following results:
Multiply:-1 *-2 = 2
Plus:-1 +-2 =-3
Multiply: 9*10 = 90
Plus:-9 +-10 =-19
Multiply:-7 *-8 = 56
Plus:-5 +-6 =-11
Multiply:-3 *-4 = 12
Plus: 9 + 10 = 19
Plus: 7 + 8 = 15
Plus: 5 + 6 = 11
We can see the competition between threads. The value is not displayed in the original order because the value of size is randomly modified by various threads. This is often a problem that needs to be paid attention to in multi-threaded programming.
Summary
Multi-threaded programming is a very interesting and useful technology. Network ant financial, which uses multithreading technology, is one of the most commonly used download tools, the grep using multithreading technology is several times faster than the single-thread grep. There are many other similar examples. I hope you can use multithreading technology to write efficient and practical programs.
This article from the day pole: http://dev.yesky.com/331/2276331_2.shtml