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 threadProgramThere 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 */
Pthread_key_t mywinkey;
/* Function createwindow */
Void createwindow (void ){
Fl_window * Win;
Static pthread_once_t once = pthread_once_init;
/* Call the createmykey function to create a key */
Pthread_once (& once, createmykey );
/* Win points to a new window */
Win = new fl_window (0, 0,100,100, "mywindow ");
/* Make some possible settings for this window, such as the size, position, and name */
Setwindow (WIN );
/* Bind the window pointer value to the key mywinkey */
Pthread_setpecific (mywinkey, Win );
}
/* The createmykey function creates a key and specifies the Destructor */
Void createmykey (void ){
Pthread_keycreate (& mywinkey, freewinkey );
}
/* Function freewinkey, release space */
Void freewinkey (fl_window * Win ){
Delete win;
}
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 ));
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 for a period of timeCode. 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 );
Void writer_function (void );
Char buffer;
Int buffer_has_item = 0;
Pthread_mutex_t mutex;
Struct timespec delay;
Void main (void ){
Pthread_t reader;
/* Define the latency */
Delay. TV _sec = 2;
Delay. TV _nec = 0;
/* Use the default attribute to initialize a mutex lock object */
Pthread_mutex_init (& mutex, null );
Pthread_create (& reader, pthread_attr_default, (void *) & reader_function), null );
Writer_function ();
}
Void writer_function (void ){
While (1 ){
/* Lock the mutex lock */
Pthread_mutex_lock (& mutex );
If (buffer_has_item = 0 ){
Buffer = make_new_item ();
Buffer_has_item = 1;
}
/* Open the mutex lock */
Pthread_mutex_unlock (& mutex );
Pthread_delay_np (& delay );
}
}
Void reader_function (void ){
While (1 ){
Pthread_mutex_lock (& mutex );
If (buffer_has_item = 1 ){
Consume_item (buffer );
Buffer_has_item = 0;
}
Pthread_mutex_unlock (& mutex );
Pthread_delay_np (& delay );
}
}
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.