1 Introduction
Thread technology was proposed as early as 1960s, but the real application of multithreading to the operating system was in the middle of 1980s, and Solaris was a leader in this field. Traditional UNIX also supports the concept of thread, but only one thread is allowed in a process. In this way, multithreading is intended for multiple processes. Now, multithreading technology has been supported by many operating systems, including Windows/NT and, of course, Linux.
Why do we need to introduce threads after the concept of a process is introduced? What are the advantages of multithreading? What systems should adopt multithreading? We must answer these questions first.
One of the reasons for using multithreading is that compared with processes, it is a very "frugal" multi-task operation method. We know that in a Linux system, starting a new process must be assigned to it an independent address space, and a large number of data tables are created to maintain its code segment, stack segment, and data segment, this is an "expensive" way of multitasking. While multiple threads running in a process use the same address space for each other to share most of the data. The space required to start a thread is much less than the space required to start a process, in addition, the time required for switching between threads is much less than the time required for switching between processes. According to statistics, in general, the overhead of a process is about 30 times the overhead of a thread. Of course, this data may be quite different in a specific system.
The second reason for using multithreading is the convenient communication mechanism between threads. For different processes, they have independent data space, and data transmission can only be performed through communication. This method is not only time-consuming, but also inconvenient. The thread is not the case. Because the threads in the same process share data space, the data of one thread can be directly used by other threads, which is fast and convenient. Of course, data sharing also brings about other problems. Some Variables cannot be modified by two threads at the same time, and some subprograms declare static data, which is more likely to cause catastrophic damage to multithreaded programs, these are the things you need to pay attention to when writing multi-threaded programs.
In addition to the advantages mentioned above, multi-threaded programs, as a multi-task and concurrent working method, certainly have the following advantages:
1) Improve application response. This is especially meaningful for graphic interface programs. When an operation takes a long time, the entire system will wait for this operation. At this time, the program will not respond to keyboard, mouse, and menu operations, but will use multithreading technology, putting time consuming in a new thread can avoid this embarrassing situation.
2) Make the multi-CPU system more effective. The operating system ensures that different threads run on different CPUs when the number of threads is not greater than the number of CPUs.
3) Improve the program structure. A long and complex process can be considered to be divided into multiple threads and become several independent or semi-independent running parts. Such a program will facilitate understanding and modification.
Next we will try to write a simple multi-threaded program.
2. Simple multi-thread programming
Multithreading in Linux follows the POSIX thread interface, which is called pthread. To compile a multi-threaded program in Linux, you need to use the header file pthread. H. You need to use the library libpthread. A for connection. By the way, the implementation of pthread in Linux is achieved by calling clone. Clone () is a Linux-specific system call. It is used in a similar way as fork. For details about clone (), interested readers can refer to the relevant documentation. The following is a simple multi-threaded program example1.c.
/* Example. C */
# Include <pthread. h>
# Include <stdio. h>
Void thread (void)
{
Int I;
For (I = 0; I <3; I ++)
Printf ("This Is A pthread./N ");
}
Int main (void)
{
Pthread_t ID;
Int I, RET;
Ret = pthread_create (& ID, null, (void *) thread, null );
If (Ret! = 0 ){
Printf ("create pthread error! /N ");
Exit (1 );
}
For (I = 0; I <3; I ++)
Printf ("this is the main process./N ");
Pthread_join (ID, null );
Return (0 );
}
We compile this program:
GCC example1.c-lpthread-O example1
Run example1 and we get the following results:
This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
Run again and we may get the following results:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
The two results are different, which is the result of two threads competing for CPU resources. In the above example, we used two functions, pthread_create and pthread_join, and declared a variable of the pthread_t type.
Pthread_t is defined in the header file/usr/include/bits/pthreadtypes. h:
Typedef unsigned long int pthread_t;
It is the identifier of a thread. The pthread_create function is used to create a thread. Its prototype is:
Extern int pthread_create _ p (pthread_t * _ thread, _ const pthread_attr_t * _ ATTR,
Void * (* _ start_routine) (void *), void * _ Arg ));
The first parameter is the pointer to the thread identifier. The second parameter is used to set the thread attribute. The third parameter is the starting address of the thread running function, and the last parameter is the parameter of the running function. Here, our function thread does not need a parameter, so the last parameter is set as a null pointer. We also set the second parameter as a null pointer to generate a thread with the default attribute. The setting and modification of thread attributes will be described in the next section. When the thread is successfully created, the function returns 0. If the value is not 0, the thread creation fails. The common error codes returned are eagain and einval. The former indicates that the system restricts the creation of new threads. For example, the number of threads is too large. The latter indicates that the second parameter indicates that the thread attribute value is invalid. After the thread is successfully created, the newly created thread runs the function with parameters 3 and 4, and the original thread continues to run the next line of code.
The pthread_join function is used to wait for the end of a thread. Function prototype:
Extern int pthread_join _ p (pthread_t _ th, void ** _ thread_return ));
The first parameter is the identifier of the waiting thread, and the second parameter is a user-defined pointer, which can be used to store the return value of the waiting thread. This function is a thread-blocking function. The function called will wait until the end of the waiting thread. When the function returns, the resources of the waiting thread will be reclaimed. There are two ways to end a thread. One is that the function ends and the thread that calls it ends, as in the preceding example; another method is to use the pthread_exit function. Its function prototype is:
Extern void pthread_exit _ p (void * _ retval) _ attribute _ (_ noreturn __));
The unique parameter is the return code of the function. As long as the second thread_return parameter in pthread_join is not null, this value will be passed to thread_return. Finally, it should be noted that a thread cannot be waited by multiple threads. Otherwise, the first thread that receives the signal will return success, and the other threads that call pthread_join will return the error code esrch.
In this section, we write a simple thread and master the three most commonly used functions pthread_create, pthread_join, and pthread_exit. Next, let's take a look at some common attributes of the thread and how to set these attributes.
3. modify attributes of a thread
In the example in the previous section, we used the pthread_create function to create a thread. In this thread, we used the default parameter to set the second parameter of the function to null. Indeed, for most programs, it is enough to use the default attribute, but we still need to understand the relevant attributes of the thread.
The property structure is pthread_attr_t, which is also defined in the header file/usr/include/pthread. H. You can check the attribute structure by yourself. Attribute values cannot be set directly. Related functions must be used for operations. The initialized function is pthread_attr_init. This function must be called before the pthread_create function. Attribute objects mainly include binding, splitting, stack address, stack size, and priority. The default attributes are non-bound, non-separated, 1 MB stacks by default, and have the same priority as the parent process.
Thread binding involves another concept: lwp: Light Weight process ). A lightweight process can be understood as a kernel thread, which is located between the user layer and the system layer. The system allocates thread resources and controls threads through lightweight processes. A lightweight process can control one or more threads. By default, the number of light processes started and the light processes to control which threads are controlled by the system are called unbound. Under the binding condition, a thread is bound to a light process. The bound thread has a high response speed because the CPU time slice is scheduled to light processes. The bound thread can ensure that there is always a light process available when needed. By setting the priority and scheduling level of the bound process, the bound thread can meet requirements such as real-time response.
The function used to set the thread binding status is pthread_attr_setscope. It has two parameters: the first is the pointer to the attribute structure, and the second is the binding type. It has two values: pthread_scope_system (bound) and pthread_scope_process (unbound ). The following code creates a bound thread.
# Include <pthread. h>
Pthread_attr_t ATTR;
Pthread_t tid;
/* Initialize the property value, which is set to the default value */
Pthread_attr_init (& ATTR );
Pthread_attr_setscope (& ATTR, pthread_scope_system );
Pthread_create (& tid, & ATTR, (void *) my_function, null );
The separation status of a thread determines how a thread terminates itself. In the preceding example, we use the default attribute of the thread, that is, the thread is not in the detached state. In this case, the original thread waits for the creation of the thread to end. Only when the pthread_join () function returns, the created thread is terminated and the system resources occupied by it can be released. The separation thread is not like this. It is not waiting by other threads. When the running ends, the thread is terminated and system resources are immediately released. Programmers should select appropriate separation States based on their own needs. The function used to set the thread separation status isPthread_attr_setdetachstate (pthread_attr_t * ATTR, int detachstate). The second parameter can be pthread_create_detached and pthread _ create_joinable ).