Linux multi-thread programming (not limited to Linux)

Source: Internet
Author: User

 

-- An Example of this article is used to describe thread operations, multi-thread synchronization, and mutual exclusion in Linux.

Preface

Thread? Why do processes still need threads? What are their differences? What are the advantages of using threads? There are also some details about multi-threaded programming, such as how to synchronize and mutex between threads. These things will be described in this article. I saw this interview question in a QQ group:

Familiar with POSIX multi-thread programming technology? If you are familiar with it, write a program to complete the following functions:

1) there is an int type global variable g_Flag whose initial value is 0;

2) start thread 1 in the main line name, print "this is thread1", and set g_Flag to 1

3) start thread 2 in the main line name, print "this is thread2", and set g_Flag to 2.

4) thread 1 can exit only after thread 2 exits.

5) The main thread exits when detecting that g_Flag is changed from 1 to 2 or from 2 to 1.

Let's start this article with this question. After that, everyone will do it. The framework of this article is as follows:

  • 1. Process and thread
  • 2. Reasons for using threads
  • 3. functions related to thread operations
  • 4. Mutual Exclusion between threads
  • 5. synchronization between threads
  • 6. Final question code
1. Process and thread

A process is an instance of program execution, that is, the collection of data structures to which the program has been executed. From the kernel point of view, the process aims to serve as the basic unit for allocating system resources (such as CPU time and memory.

A thread is an execution flow of a process and the basic unit of CPU scheduling and dispatching. It is a basic unit that can run independently less than a process. A process is composed of several threads (User Programs with many relatively independent execution streams share most of the data structure of the application ), the thread shares all the resources of the process with other threads of the same process.

"Process -- minimum unit of resource allocation, thread -- minimum unit of Program Execution"

A process has an independent address space. After a process crashes, it will not affect other processes in the protection mode, but the thread is only a different execution path in the process. The thread has its own stack and local variables, but the thread does not have a separate address space. When a thread dies, the whole process dies. Therefore, the multi-process program is more robust than the multi-thread program, however, during process switching, resources are consumed and the efficiency is lower. But for some concurrent operations that require simultaneous and shared variables, you can only use threads, not processes.

2. Reasons for using threads

From the above, we know the differences between processes and threads. In fact, these differences are also the reasons for using threads. In general, the process has an independent address space, and the thread does not have a separate address space (the thread in the same process shares the address space of the process ). (The following content is taken from multi-thread programming in Linux)

MultithreadingOne reasonCompared with processes, it is a very "frugal" multi-task operation method. We know that in a Linux system, starting a new process must be allocated to it with an independent address space, creating a large number of data tables 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.

MultithreadingReason 2It is a 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:

  • 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.
  • 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.
  • Improve 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.

==================================

In terms of function calls, fork () is used for process creation, and clone () is used for thread creation. Master Richard Stevens said:

  • ForkIs expensive. memory is copied from the parent to the child, all descriptors are duplicated in the child, and so on. current implementations use a technique called copy-on-write, which avoids a copy of the parent's Data Space
    To the child until the child needs its own copy. But, regardless of this optimization,
    ForkIs expensive.

  • IPC is required to pass information between the parent and child after
    Fork. Passing information from the parent to the child beforeForkIs easy, since the child starts with a copy of the parent's data space and with a copy of all the parent's descriptors. But, returning information from the child to
    Parent takes more work.

Threads help with both problems. Threads are sometimes called lightweight processes since a thread is "lighter weight" than a process. That is, thread creation can be 10-100 times faster than process creation.

All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem of synchronization.

==================================

3. functions related to thread operations
#include <pthread.h> int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);int pthread_join (pthread_t tid, void ** status);pthread_t pthread_self (void);int pthread_detach (pthread_t tid);void pthread_exit (void *status);

Pthread_create is used to create a thread. If a thread is successfully created, 0 is returned. Otherwise, Exxx (positive number) is returned ).

  • Pthread_t * tid: the thread id type is pthread_t, which is usually an unsigned integer. When pthread_create is called successfully, it is returned through the * tid pointer.
  • Const pthread_attr_t * attr: Specifies the attributes of the created thread, such as the thread priority, initial stack size, and whether the thread is a daemon. You can use NULL to use the default value. Generally, we use the default value.
  • Void * (* func) (void *): function pointer func, which specifies the function to be executed after a new thread is created.
  • Void * arg: the parameter of the function to be executed by the thread. To pass multiple parameters, encapsulate them in a struct.

Pthread_join is used to wait for a thread to exit. If it succeeds, 0 is returned. Otherwise, Exxx (positive number) is returned ).

  • Pthread_t tid: Specifies the ID of the thread to wait
  • Void ** status: if it is not NULL, the return value of the thread is stored in the space pointed to by status (that is why status is a second-level pointer! This parameter is also called the "value-result" parameter ).

Pthread_self is used to return the ID of the current thread.

Pthread_detach is used to change the specified threadSeparationStatus, as if the process is changed from the terminal to the background process. 0 is returned. Otherwise, Exxx is returned (positive ). The thread that changes to the detached state. If the thread exits, all its resources will be released. Otherwise, the thread must keep its thread ID and exit until other threads call pthread_join.

The process is also similar. This is why many dead processes are found when we open the process manager! This is also why the process must be frozen.

Pthread_exit is used to terminate a thread. You can specify the return value so that other threads can use the pthread_join function to obtain the return value of the thread.

  • Void * status: the return value of the pointer thread termination.

After learning about these functions, we try to solve the problem at the beginning of this article:

1) there is an int type global variable g_Flag whose initial value is 0;

2) start thread 1 in the main line name, print "this is thread1", and set g_Flag to 1

3) start thread 2 in the main line name, print "this is thread2", and set g_Flag to 2.

These three points are very simple !!! It is not to call pthread_create to create a thread. The Code is as follows:

/** 1) there is an int type global variable g_flag whose initial value is 0; ** 2) start thread 1 in the main line name, print "this is thread1 ", set g_flag to 1 ** 3) start thread 2 in the main line name, print "this is thread2", and set g_flag to 2 **/# include <stdio. h> # include <stdlib. h> # include <pthread. h> # include <errno. h> # include <unistd. h> int g_flag = 0; void * thread1 (void *); void * thread2 (void *);/** when program is started, a single thread is created, called the initial thread or main thread. * Additional threads ar E created by pthread_create. * So we just need to create two thread in main (). */INT main (INT argc, char ** argv) {printf ("Enter main \ n"); pthread_t tid1, tid2; int RC1 = 0, RC2 = 0; RC2 = pthread_create (& tid2, null, thread2, null); If (RC2! = 0) printf ("% s: % d \ n" ,__ func __, strerror (RC2); RC1 = pthread_create (& tid1, null, thread1, & tid2 ); if (RC1! = 0) printf ("% s: % d \ n" ,__ func __, strerror (RC1); printf ("Leave main \ n "); exit (0);}/** thread1 () will be execute by thread1, after pthread_create () * It will set g_flag = 1; */void * thread1 (void * Arg) {printf ("Enter thread1 \ n"); printf ("this is thread1, g_flag: % d, thread ID is % u \ n", g_flag, (unsigned INT) pthread_self (); g_flag = 1; printf ("this is thread1, g_flag: % d, thread ID is % u \ n", g_flag, (unsigned INT) pthread_self (); printf ("Leave thread1 \ n"); pthread_exit (0);}/** thread2 () will be execute by thread2, after pthread_create () * It will set g_flag = 2; */void * thread2 (void * Arg) {printf ("Enter thread2 \ n"); printf ("this is thread2, g_flag: % d, thread ID is % u \ n ", g_flag, (unsigned INT) pthread_self (); g_flag = 2; printf (" this is thread1, g_flag: % d, thread ID is % u \ n ", g_flag, (unsigned INT) pthread_self (); printf (" Leave thread2 \ n "); pthread_exit (0 );}

This completes the three requirements: 1), 2), and 3. Compile and execute the following results:

Netsky @ ubuntu :~ /Workspace/pthead_test $ gcc-lpthread test. c

If the function in the pthread library is used in the program, in addition to # include <pthread. h>, the-lpthread option is added during compilation.

Netsky @ ubuntu :~ /Workspace/pthead_test $./a. out
Enter main
Enter thread2
This is thread2, g_Flag: 0, thread id is 3079588720
This is thread1, g_Flag: 2, thread id is 3079588720
Leave thread2
Leave main
Enter thread1
This is thread1, g_Flag: 2, thread id is 3071196016
This is thread1, g_Flag: 1, thread id is 3071196016
Leave thread1
However, the running result is not necessarily the above, and may be:

Netsky @ ubuntu :~ /Workspace/pthead_test $./a. out
Enter main
Leave main
Enter thread1
This is thread1, g_Flag: 0, thread id is 3069176688
This is thread1, g_flag: 1, thread ID is 3069176688
Leave thread1

Or:

Netsky @ Ubuntu :~ /Workspace/pthead_test $./A. Out
Enter main
Leave main
And so on. This is also easy to understand, because it depends on when the main function of the main thread is terminated, and whether the threads thread1 and thread2 can urgently execute their functions. This is also a problem to pay attention to when programming with multiple threads, because it is possible that one thread will affect all other threads in the whole process! If we sleep () for a while before exiting the main function, we can ensure that thread1 and thread2 can be executed in time.

Attention: You must have noticed that we have called pthread_exit before the thread functions thread1 () and thread2 () are executed. What if I call exit () or return? Try it on your own!

Pthread_exit () is used to exit a thread. You can specify the return value so that other threads can use the pthread_join () function to obtain the return value of the thread.
Return is the function return. Only the return of the thread function will exit.
Exit is a process exit. If exit is called in a thread function, all functions in the process exit!

"4) thread 1 needs to exit after thread 2 exits." Point 4th is also easy to solve. It is OK to call pthread_join directly before thread1's function exits.

4. Mutual Exclusion between threads

The above Code seems to have solved the first four requirements of the problem, but it is not actually !!! G_flag is a global variable. threads thread1 and thread2 can operate on it at the same time. They need to be locked. thread1 and thread2 must be mutually exclusive. Next we will introduce how to lock protection-mutex lock.

Mutex lock:

The use of mutex lock (mutex) allows the thread to be executed in order. Generally, mutex locks synchronize multiple threads by ensuring that there is only one thread to execute code at a time. The mutex lock can also protect single-threaded code.

The related operation functions of mutex lock are as follows:

#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t * mptr); int pthread_mutex_unlock(pthread_mutex_t * mptr); //Both return: 0 if OK, positive Exxx value on error

Before performing operations on critical resources, You need to lock pthread_mutex_lock and then unlock pthread_mutex_unlock. Before that, a variable of the pthread_mutex_t type must be declared and used as the parameters of the previous two functions. For more information about the code, see section 5th.

5. synchronization between threads

-- The main thread exits when detecting that g_flag is changed from 1 to 2 or from 2 to 1. Thread Synchronization Technology is required! Condition variables are required for thread synchronization.

Condition variable:

Conditional variables can be used to intercept threads in an atomic manner until a specific condition is true. Condition variables are always used together with mutex locks. The condition test is performed under the protection of mutex lock (mutex.

If the condition is false, the thread usually Blocks Based on the condition variable and releases the mutex lock waiting for the condition change in an atomic manner. If another thread changes the condition, the thread may send a signal to the related condition variable, so that one or more waiting threads can perform the following operations:

  • Wake up
  • Obtain mutex again
  • Re-evaluation Conditions

In the following cases, condition variables can be used to synchronize threads between processes:

  • The thread is allocated in the writable memory.
  • Memory shared by the collaboration process

"Conditional variables can be used to intercept threads in an atomic manner until a specific condition is true ." You can use the 5th point. The main function of the main thread is blocked when the g_flag changes from 1 to 2, or from 2 to 1. The related functions of conditional variables are as follows:

#include <pthread.h> int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); int pthread_cond_signal(pthread_cond_t *cptr); //Both return: 0 if OK, positive Exxx value on error

Pthread_cond_wait is used to wait for a specific condition to be true, and pthread_cond_signal is used to notify the blocked thread that a specific condition is true. Before calling two functions, you must declare a variable of the pthread_cond_t type for the parameters of these two functions.

Why is the condition variable always used together with the mutex lock? The test of the condition is under the protection of the mutex lock (mutex? Because "a special condition" is usually a variable shared among multiple threads. Mutex lock allows this variable to be set and detected in different threads.

Generally, pthread_cond_wait only wakes up a thread waiting for a condition variable. To wake up all threads waiting for a condition variable, call:

int pthread_cond_broadcast (pthread_cond_t * cptr);

By default, the blocked thread waits until it knows that a condition variable is true. To set the maximum blocking time, call:

int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);

If the time is reached and the condition variable is not true, the return value isETIME.

6. Final question code

With the previous introduction, we can easily write the code as follows:

/* Are you familiar with POSIX multi-threaded programming technology? If you are familiar with it, write the program to complete the following functions: 1) There is an int type global variable g_Flag whose initial value is 0; 2) start thread 1 in the main line name and print "this is thread1 ", set g_Flag to 1. 3) start thread 2 in the main line name, print "this is thread2", and set g_Flag to 2 4) thread 1 needs to exit after thread 2 exits. 5) when the main thread detects that g_Flag is changed from 1 to 2 or from 2 to 1, it exits */# include <stdio. h> # include <stdlib. h> # include <pthread. h> # include <errno. h> # include <unistd. h> typedef void * (* fun) (void *); int g_Flag = 0; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t con D = PTHREAD_COND_INITIALIZER; void * thread1 (void *); void * thread2 (void *);/** when program is started, a single thread is created, called the initial thread or main thread. * Additional threads are created by pthread_create. * So we just need to create two thread in main (). */int main (int argc, char ** argv) {printf ("enter main \ n"); pthread_t tid1, tid2; int rc1 = 0, rc2 = 0; rc2 = pthread_create (& tid2, NULL, Thread2, NULL); if (rc2! = 0) printf ("% s: % d \ n" ,__ func __, strerror (rc2); rc1 = pthread_create (& tid1, NULL, thread1, & tid2 ); if (rc1! = 0) printf ("% s: % d \ n" ,__ func __, strerror (rc1); pthread_cond_wait (& cond, & mutex ); printf ("leave main \ n"); exit (0);}/** thread1 () will be execute by thread1, after pthread_create () * it will set g_Flag = 1; */void * thread1 (void * arg) {printf ("enter thread1 \ n"); printf ("this is thread1, g_Flag: % d, thread id is % u \ n ", g_Flag, (unsigned int) pthread_self (); pthread_mutex_lock (& mutex); if (g_Flag = 2) pthread_cond_signal (& cond ); g_Flag = 1; printf ("this is thread1, g_Flag: % d, thread id is % u \ n", g_Flag, (unsigned int) pthread_self ()); pthread_mutex_unlock (& mutex); pthread_join (* (pthread_t *) arg, NULL); printf ("leave thread1 \ n"); pthread_exit (0);}/** thread2 () will be execute by thread2, after pthread_create () * it will set g_Flag = 2; */void * thread2 (void * arg) {printf ("enter thread2 \ n "); printf ("this is thread2, g_Flag: % d, thread id is % u \ n", g_Flag, (unsigned int) pthread_self (); pthread_mutex_lock (& mutex ); if (g_Flag = 1) pthread_cond_signal (& cond); g_Flag = 2; printf ("this is thread2, g_Flag: % d, thread id is % u \ n", g_Flag, (unsigned int) pthread_self (); pthread_mutex_unlock (& mutex); printf ("leave thread2 \ n"); pthread_exit (0 );}

Compile and run the program to get the expected results!

-- This log is a gift for my birthday!

Come on, work hard, don't give up!

Author: Wu Qin
Source: http://www.cnblogs.com/skynet/
This article was published based on the signature 2.5 mainland China license agreement. You are welcome to reprint, interpret, or use it for commercial purposes. However, you must keep the signature Wu Qin (including the link) of this Article ).

 
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.