Preface

Threads ? Why do we need threads when we have processes , What's the difference between them ? What are the advantages of using threads ? There are also some details of multithreading programming , Such as how to synchronize between threads 、 Mutually exclusive , These things will be introduced in this article . I'm somewhere QQ I see such an interview question in the group :

Are you familiar with POSIX Multithreading programming technology ? If you are familiar with , Write a program to complete the following functions :

1) There is a int Type a global variable g_Flag The initial value is 0;

2) Start the thread in the main thread 1, Print “this is thread1”, And will g_Flag Set to 1

3) Start the thread in the main thread 2, Print “this is thread2”, And will g_Flag Set to 2

4) Line program 1 Need in thread 2 You can't quit until you quit

5) The main thread is detecting g_Flag from 1 Turn into 2, Or from 2 Turn into 1 Quit when you're on the road

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

  • 1、 Processes and threads
  • 2、 The reason to use threads
  • 3、 Functions about thread operation
  • 4、 Mutual exclusion between threads
  • 5、 Synchronization between threads
  • 6、 The final code of the test

1、 Processes and threads

A process is an instance of the execution of a program , That is, it is a collection of data structures to what extent the program has been executed . From a kernel point of view , The purpose of a process is to allocate system resources (CPU Time 、 Memory, etc. ) The basic unit of .

A thread is an execution flow of a process , yes CPU Basic unit of dispatch and dispatch , It's a smaller, independent, basic unit than a process . A process consists of several threads ( User programs with many relatively independent execution streams share most of the data structures of the application ), A thread shares all the resources owned by a process with other threads belonging to the same process .

" process —— The smallest unit of resource allocation , Threads —— The smallest unit of program execution "

The process has a separate address space , After a process crashes , No impact on other processes in protected mode , Threads are just different execution paths in a process . Threads have their own stack and local variables , But threads don't have a separate address space , If one thread dies, the whole process will die , So a multiprocess program is more robust than a multithreaded program , But when the process switches , High cost of resources , Less efficient . But for some concurrent operations that require simultaneous and shared variables , Only threads can be used , Can't use process .

2、 The reason to use threads

From the above we know the difference between process and thread , In fact, these differences are the reasons why we use threads . In general : The process has a separate address space , Threads have no separate address space ( Threads in the same process share the address space of the process ).( Here's an excerpt from Linux Multi thread programming under

Use multithreaded One of the reasons It's compared to the process , It's a very " frugal " Multi task operation mode of . We know , stay Linux Under the system , Starting a new process must be assigned a separate address space , Build a large number of data tables to maintain its code segments 、 Stack and data segments , This is a kind of " expensive " The multitasking way of working . And multiple threads running in one process , They use the same address space as each other , Share most of the data , Starting a thread takes far less space than starting a process , and , The time required for threads to switch between each other is also far less than the time required for processes to switch . According to statistics , On the whole , The cost of a process is about that of a thread 30 About times , Of course , On a specific system , This data may make a big difference .

Use multithreaded The second reason is It's a convenient communication mechanism between threads . For different processes , They have independent data space , The transmission of data can only be done by means of communication , This way is not only time-consuming , And it's inconvenient . Threads are not , Because the threads under the same process share data space , So the data of one thread can be directly used by other threads , It's not only fast , And convenient . Of course , Data sharing also brings about other problems , Some variables cannot be modified by two threads at the same time , Some subroutines are declared as static More likely to be disastrous for multithreaded programs , These are the most important things to pay attention to when writing multithreaded programs .

In addition to the advantages mentioned above , Don't compare with the process , Multithreaded program as a multitask 、 Concurrent way of working , Of course, there are the following advantages :

  • Improve application response . This is especially meaningful for GUI programs , When an operation takes a long time , The whole system will wait for this operation , The program will not respond to the keyboard at this time 、 mouse 、 Menu operation , And using multithreading technology , Will take a long time to operate (time consuming) Put in a new thread , This embarrassing situation can be avoided .
  • Make many CPU The system is more efficient . The operating system will ensure that when the number of threads is not greater than CPU In number , Different threads run in different CPU On .
  • Improve program structure . A long and complex process can be divided into multiple threads , Become several independent or semi independent operating parts , Such a program will help to understand and modify .

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

In terms of function calls , Process creation using fork() operation ; Thread creation and use clone() operation .Richard Stevens The master said so :

  • fork is 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, fork is expensive.

  • IPC is required to pass information between the parent and child after the fork. Passing information from the parent to the child before the fork is 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 the 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 about thread operation

#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 Used to create a thread , Successfully returns 0, Otherwise return to Exxx( Is a positive number ).

  • pthread_t *tid: Threads id The type of pthread_t, It is usually an unsigned integer , When calling pthread_create success , adopt *tid The pointer returns .
  • const pthread_attr_t *attr: Specifies the properties of the creation thread , Such as thread priority 、 Initial stack size 、 Whether it's a daemon or not . have access to NULL To use the default value , Usually we use the default value .
  • void *(*func) (void *): A function pointer func, Specifies that when a new thread is created , The function to be executed .
  • void *arg: Parameters of the function that the thread will execute . If you want to pass multiple parameters , Please encapsulate them in a structure .

pthread_join Used to wait for a thread to exit , Successfully returns 0, Otherwise return to Exxx( Is a positive number ).

  • pthread_t tid: Specify the thread to wait for ID
  • void ** status: If not for NULL, Then the return value of the thread is stored in status Pointing in the space ( That's why status It's the second level pointer ! This parameter is also called “ value - result ” Parameters ).

pthread_self Used to return the current thread ID.

pthread_detach Used to change the specified thread to Separate state , It's like a process leaving the terminal and becoming a background process . Successfully returns 0, Otherwise return to Exxx( Is a positive number ). Threads that become detached , If the thread exits , All its resources will be released . And if it's not a state of separation , A thread must keep its thread ID, Exit state until other threads call it pthread_join.

The process is similar , This is also when we open the process manager , It turns out that there are a lot of reasons for the dead process ! It's also why there must be a process state of rigidity .

pthread_exit Used to terminate a thread , You can specify the return value , So that other threads can pass through pthread_join Function to get the return value of the thread .

  • void *status: Return value of pointer thread termination .

After knowing these functions , We are trying to finish the problem at the beginning of this article :

1) There is a int Type a global variable g_Flag The initial value is 0;

2) Start the thread in the main thread 1, Print “this is thread1”, And will g_Flag Set to 1

3) Start the thread in the main thread 2, Print “this is thread2”, And will g_Flag Set to 2

this 3 That's easy !!! It's called pthread_create Create thread . The code is as follows :

/*
 * 1) There is a int Type a global variable g_Flag The initial value is 0;
 *
 * 2) Start the thread in the main thread 1, Print “this is thread1”, And will g_Flag Set to 1
 *
 * 3) Start the thread in the main thread 2, Print “this is thread2”, And will g_Flag Set 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 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));
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);
}

That's it 1)、2)、3) These three requirements . The result of compilation is as follows :

[email protected]:~/workspace/pthead_test$ gcc -lpthread test.c

If it is used in the program pthread Functions in the library , Except for #include<pthread.h>, When compiling, we also add -lpthread  Options .
[email protected]:~/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
But the running results are not necessarily the above , And it could be :

[email protected]:~/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 is it :

[email protected]:~/workspace/pthead_test$ ./a.out
enter main
leave main
wait . And that's understandable because , It depends on the main thread main When does the function terminate , Threads thread1、thread2 Whether they can execute their functions in a hurry . This is also a problem to pay attention to when multithreading programming , Because it is possible that one thread will affect all other threads in the whole process ! If we were main Before function exit ,sleep() A span , You can guarantee thread1、thread2 There's time to do it .

 vampire bat Attention: You must have noticed , We use thread functions thread1()、thread2() It's called before it's finished pthread_exit. If I was calling exit() Or is return What will happen? ? Try it yourself !

pthread_exit() For thread exit , You can specify the return value , So that other threads can pass through pthread_join() Function to get the return value of the thread .
return It's a function that returns , Only thread functions return, The thread will exit .
exit Process exit , If called in thread function exit, All functions in the process exit !

“4) Line program 1 Need in thread 2 You can't quit until you quit ” The first 4 It's easy to solve this problem , Directly in thread1 Call before the function exits pthread_join Just OK 了 .

4、 Mutual exclusion between threads

The above code seems to be a good solution to the front of the problem 4 Point requirements , It's not !!! because g_Flag Is a global variable , Threads thread1 and thread2 It can be operated at the same time , It needs to be locked ,thread1 and thread2 Exclusive access . Now we will introduce how to lock protection —— The mutex .

The mutex :

Use mutexes ( Mutually exclusive ) You can make threads execute in order . Usually , Mutex synchronizes multiple threads by ensuring that only one thread executes critical segments of code at a time . Mutexes also protect single threaded code .

The related operation functions of mutex 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 operating on critical resources, you need to pthread_mutex_lock Lock first , After operation pthread_mutex_unlock Unlock again . And before that, we need to declare a pthread_mutex_t Variable of type , As arguments to the first two functions . The specific code is shown in section 5 section .

5、 Synchronization between threads

The first 5 spot —— The main thread is detecting g_Flag from 1 Turn into 2, Or from 2 Turn into 1 Quit when you're on the road . We need to use thread synchronization technology ! Thread synchronization requires condition variables .

Condition variables, :

Use condition variables to block threads atomically , Until a particular condition is true . Conditional variables are always used with mutexes . The test of conditions is in mutex ( Mutually exclusive ) Under the protection of .

If the condition is false , Threads usually block based on conditional variables , And release mutually exclusive locks waiting for conditions to change atomically . If another thread changes the condition , The thread may signal the relevant condition variable , This causes one or more waiting threads to do the following :

  • Wake up the
  • Get the mutex again
  • Reassess conditions

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

  • Threads are allocated in writable memory
  • Memory is shared by collaborative processes

“ Use condition variables to block threads atomically , Until a particular condition is true .” You can use the second 5 spot , The main thread main Function blocked waiting g_Flag from 1 Turn into 2, Or from 2 Turn into 1. The correlation function of the conditional variable is 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 Used to wait for a specific condition to be true ,pthread_cond_signal A specific condition used to notify the blocked thread is true . Before calling two functions, you need to declare a pthread_cond_t Variable of type , Parameters for both functions .

Why conditional variables are always used with mutexes , The test of conditions is in mutex ( Mutually exclusive ) Under the protection of ? because “ A characteristic condition ” It's usually a variable shared among multiple threads . Mutex allows this variable to be set and detected in different threads .

Usually ,pthread_cond_wait Just wake up a thread waiting for a condition variable . If you need to wake up all threads waiting for a condition variable , Need to call :

int pthread_cond_broadcast (pthread_cond_t * cptr);

By default, the following is true , Blocked threads wait , Know that a conditional variable is true . If you want to set the maximum blocking time, you can call :

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

If it's time , The conditional variable is not true yet , Still back , The return value is ETIME.

6、 The final code of the test

Through the previous introduction , We can write code easily , As shown below :

/*
  Are you familiar with POSIX Multithreading programming technology ? If you are familiar with , Write a program to complete the following functions :
  1) There is a int Type a global variable g_Flag The initial value is 0;
  2) Start the thread in the main thread 1, Print “this is thread1”, And will g_Flag Set to 1
  3) Start the thread in the main thread 2, Print “this is thread2”, And will g_Flag Set to 2
  4) Line program 1 Need in thread 2 You can't quit until you quit
  5) The main thread is detecting g_Flag from 1 Turn into 2, Or from 2 Turn into 1 Quit when you're on the road
   */
#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 cond = 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);
}

Compiling and running can get satisfactory results !

—— This diary is a gift for my birthday !

come on. , Strive , Don't give up !

 

author : Saylor
Source :http://www.cnblogs.com/skynet/