Linux multithreaded programming

This chapter will be divided into two parts to explain. The first half will introduce the use scenarios and basic concepts of threads, and illustrate the basic process from thread creation to exit to recycling through example code. The latter part will explain through the example code that if the thread is well controlled, the concepts and usage of mutex and semaphore will be introduced from the critical resource access and thread execution order control.

1. Use of threads

1.1 why use multithreading

When writing code, will you encounter the following scenarios and feel difficult to start?

Scenario 1: when writing a program to copy a file, it needs to copy the file and show the progress of copying the file to the user at the same time. The traditional method is to update the variable after each copy, and then convert the variable into the progress display. It goes through copying - > calculation - > display - > copy - > calculation - > display... Until the end of copying. Such a program architecture and its inefficiency can refresh the current copy progress only after the end of a single copy. If you can branch the process, one to solve the copy problem and one to solve the calculation refresh problem, the program efficiency will be greatly improved.

Scenario 2: read data in blocking mode and send data in real time. For example, when carrying out serial data transmission or network data transmission, we often need two-way communication. When setting the read data as blocking mode, the traditional single thread can only rush through the blocking after the data reception, and then send it according to the logic. When we want to send and receive at any time, we cannot meet our business needs. If you can branch the process, a simple logic for processing and receiving data and a simple logic for sending data, you can perfectly realize the function.

Based on the above scenario description, multithreaded programming can perfectly solve the above problems.

1.2 thread concept

The so-called thread is the smallest unit that the operating system can schedule. In an ordinary process, only one thread is executing the corresponding logic. We can use multithreading programming to enable a process to perform multiple different tasks. Compared with multi process programming, threads enjoy shared resources, that is, global variables that appear in the process. Each thread can access it and share "4G" memory space with the process, reducing the consumption of system resources. This chapter discusses POSIX threads under Linux.

1.3 identification of thread pthread_t

For processes, each process has a unique corresponding PID number to represent the process. For threads, there is also a "PID number similar to the process", named tid, which is essentially a pthread_ Variable of type T. Thread number and process number are the unique identification of thread and process, but for thread number, it is meaningful only in the process context to which it belongs.

In the program, you can use the function pthread_self to return the thread number of the current thread. Routine 1 gives the print thread tid number.

Test routine 1: (Phtread_txex1.c)

1    #include <pthread.h>

2    #include <stdio.h>

3

4    int main()

5    {

6        pthread_t tid = pthread_self();//Get the tid number of the main thread

7        printf("tid = %lu\n",(unsigned long)tid);

8    return 0;

9    }

Note: because POSIX thread interface is adopted, pthread library is included when compiling. gcc XXX should be used for compiling C - lpthread to compile multithreaded programs.

Compilation result:

1.4 thread creation

In traditional programs, a process has only one thread, which can be accessed through the function pthread_create to create a thread.

The first parameter of this function is pthread_t-type thread number address. When the function is executed successfully, it will point to the thread number of the new thread; The second parameter represents the attribute of the thread. Generally, NULL is passed in to represent the default attribute; The third parameter represents the function pointer with the return value of void * and the formal parameter of void *. When the thread is created successfully, the callback function will be automatically executed; The fourth parameter refers to the parameter passed in to the thread processing function. If it is not passed in, it can be filled in with NULL. The subsequent sections on thread parameter passing will have a detailed description. Next, use this function to create a thread through a simple routine.

Test routine 2: (Phtread_txex2.c)

1    #include <pthread.h>

2    #include <stdio.h>

3    #include <unistd.h>

4    #include <errno.h>

5 

6    void *fun(void *arg)

7    {

8        printf("pthread_New = %lu\n",(unsigned long)pthread_self());//The tid number of the print thread

9    }

10

11   int main()

12   {

13

14       pthread_t tid1;

15       int ret = pthread_create(&tid1,NULL,fun,NULL);//Create thread

16       if(ret != 0){

17            perror("pthread_create");

18            return -1;

19       }

20

21       /*tid_main For pthread_ Thread ID obtained by self, tid_new by executing pthread_ The space pointed to by TID after creating successfully*/

22       printf("tid_main = %lu tid_new = %lu \n",(unsigned long)pthread_self(),(unsigned long)tid1);

23       

24       /**<u>*Due to the random execution sequence of threads, without sleep, the main thread may execute first, resulting in the end of the process and unable to execute to the sub thread * < / u >**/

25       sleep(1);

26

27       return 0;

28   }

29

Operation results:

Through pthread_create can indeed create a thread and execute pthread in the main thread_ The tid after create points to the thread number space, which is connected with the child thread through the function pthread_ The thread numbers printed by self are consistent.

In particular, when the main thread ends with the process, the created thread will end immediately and will not continue to execute. And the execution order of the created threads is random competition, which can not guarantee which thread will run first. The sleep function in the above code can be annotated to observe the experimental phenomenon.

After removing 25 lines of the above code, the operation result:

The above code is run three times, two of which are ended by the process and cannot execute the logic of the sub thread. The last time is the process that ends after the logic of the sub thread is executed. This shows that the execution order of threads is not controlled, and the threads generated after the end of the whole process are released. How to control thread execution will be described in the following content.

1.5 pass parameters to the thread

pthread_ The last parameter of create() is data of void type, which means that a parameter of void data type can be passed to the thread, which can be obtained in the callback function of the thread. Routine 3 illustrates how to pass in variable address and variable value to the thread.

Test routine 3: (Phtread_txex3.c)

1    #include <pthread.h>

2    #include <stdio.h>

3    #include <unistd.h>

4    #include <errno.h>

5 

6    void *fun1(void *arg)

7    {

8        printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);

9    }

10

11   void *fun2(void *arg)

12   {

13       printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);

14   }

15

16   int main()

17   {

18

19       pthread_t tid1,tid2;

20       int a = 50;

21       int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);//Create the address of the variable a passed in by the thread

22       if(ret != 0){

23            perror("pthread_create");

24            return -1;

25       }

27       ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);//Create the value of variable a passed in by the thread

28       if(ret != 0){

29            perror("pthread_create");

30            return -1;

31       }

32       sleep(1);

33       printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);

34       return 0;

35   }

36

Operation results:

This routine shows how to use the fourth parameter of the thread creation function to pass in data to the thread, for example, how to pass in value in the form of address and variable. The 21 lines of the routine code are to take the address of variable a first, and then force the type to be converted to void, and then pass it to the thread. In the callback function processed by the thread, first convert the universal pointer void into int *, and then take the address again to obtain the value of the address variable, Its essence lies in the transmission of address. In the 27 lines of routine code, the variable of type int is directly forcibly converted into void for transmission (for machines with different digits, the pointer has different word numbers, so it is necessary to convert int into long, otherwise a warning may occur). In the thread processing callback function, the void data can be directly converted into type int, which is essentially transferring the value of variable a.

The above two methods can get the desired value, but pay attention to its essence. One is address transfer and the other is value transfer. When the variable changes, the variable corresponding to the address will also change after passing the address. However, when the variable value is passed in, even if the variable referred to by the address pointer changes, the passed in is the variable value, which will not be affected by the pointer. Remember the difference between the two in the actual project. See routine 4 for details

Test routine 4: (Phtread_txex4.c)

1    #include <pthread.h>

2    #include <stdio.h>

3    #include <unistd.h>

4    #include <errno.h>

5 

6    void *fun1(void *arg)

7    {

8        while(1){

9        

10            printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);

11            sleep(1);

12       }

13   }

14

15   void *fun2(void *arg)

16   {

17       while(1){

18       

19            printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);

20            sleep(1);

21       }

22   }

23

24   int main()

25   {

26

27       pthread_t tid1,tid2;

28       int a = 50;

29       int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);

30       if(ret != 0){

31            perror("pthread_create");

32            return -1;

33       }

34       sleep(1);

35       ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);

36       if(ret != 0){

37            perror("pthread_create");

38            return -1;

39       }

40       while(1){

41            a++;

42            sleep(1);

43            printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);

44       }

45       return 0;

46   }

47

Operation results:

The above routine describes how to pass a parameter to the thread. In dealing with actual projects, we often encounter the problem of passing multiple parameters. We can pass it through the structure to solve this problem.

Test routine 5: (Phtread_txex5.c)

1    #include <pthread.h>

2    #include <stdio.h>

3    #include <unistd.h>

4    #include <string.h>

5    #include <errno.h>

6 

7    struct Stu{

8        int Id;

9        char Name[32];

10       float Mark;

11   };

12

13   void *fun1(void *arg)

14   {

15       struct Stu *tmp = (struct Stu *)arg;

16       printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);

17       

18   }

19

20   int main()

21   {

22

23        pthread_t tid1,tid2;

24       struct Stu stu;

25       stu.Id = 10000;

26       strcpy(stu.Name,"ZhangSan");

27       stu.Mark = 94.6;

28

29       int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);

30       if(ret != 0){

31            perror("pthread_create");

32            return -1;

33       }

34       printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,stu.Id,stu.Name,stu.Mark);

35       sleep(1);

36       return 0;

37   }

38

Operation results:

1.6 thread exit and recycling

There are three exit situations for threads:

The first is the end of the process, and all threads in the process will end with it.

The second is through the function pthread_exit to actively exit the thread.

The third is through the function pthread_cancel is passively terminated by another thread.

When the thread ends, the main thread can use the function pthread_join/pthread_tryjoin_np to reclaim the resources of the thread and obtain the data to be returned after the thread ends.

 This function is a thread exit function, which can pass a void*Type of data is brought to the main thread. If you choose not to send out data, you can fill the parameters with NULL. 

This function is a thread recycling function. The default state is blocking until the thread is successfully recycled. The first parameter is the tid number of the thread to be recycled, and the second parameter is to accept the data sent by the thread after recycling.

This function is a non blocking recycling function. It determines whether to recycle the thread through the return value. If the recycling is successful, it returns 0. The other parameters are the same as pthread_join is consistent.

This function passes in a tid number, which will force the thread pointed to by the tid to exit. If it is executed successfully, it will return 0.

The above description briefly introduces the API related to thread recycling. The following describes the API through routines.

Test routine 6: (Phtread_txex6.c)

1    #include <pthread.h>

2    #include <stdio.h>

3    #include <unistd.h>

4    #include <errno.h>

5 

6    void *fun1(void *arg)

7    {

8        static int tmp = 0;//static modification is required, otherwise pthread_join failed to get the correct value

9        //int tmp = 0;

10       tmp = *(int *)arg;

11       tmp+=100;

12       printf("%s:Addr = %p tmp = %d\n",__FUNCTION__,&tmp,tmp);

13       pthread_exit((void *)&tmp);//Convert the address of variable tmp to void * type

14   }

15

16

17   int main()

18   {

19

20       pthread_t tid1;

21       int a = 50;

22       void *Tmp = NULL;//Pthread_ The second parameter of join is of type void * *

23       int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);

24       if(ret != 0){

25            perror("pthread_create");

26            return -1;

27       }

28       pthread_join(tid1,&Tmp);

29       printf("%s:Addr = %p Val = %d\n",__FUNCTION__,Tmp,*(int *)Tmp);

30       return 0;

31   }

32

Operation results:

The above routine first passes the variables into the thread in the form of address through 23 lines, and makes the operation of self adding 100 in the thread. When the thread exits, it passes parameters through the thread, and uses void * type data through pthread_join accepted. This routine removes the sleep function added before because pthread_ The join function has the feature of blocking. It will not break through the blocking until the thread is successfully retracted. Therefore, it does not need to consider that the main thread will execute to 30 lines to end the process. In particular, in line 8 of the routine, when the variable is transferred out from the thread, it needs to be modified with static to continue the life cycle, otherwise the correct variable value cannot be transferred out.

Test routine 7: (Phtread_txex7.c)

1    #define _GNU_SOURCE 

2    #include <pthread.h>

3    #include <stdio.h>

4    #include <unistd.h>

5    #include <errno.h>

6 

7    void *fun(void *arg)

8    {

9        printf("Pthread:%d Come !\n",(int )(long)arg+1);

10       pthread_exit(arg);

11   }

12

13

14   int main()

15   {

16        int ret,i,flag = 0;

17       void *Tmp = NULL;

18       pthread_t tid[3];

19       for(i = 0;i < 3;i++){

20            ret = pthread_create(&tid[i],NULL,fun,(void *)(long)i);

21            if(ret != 0){

22                 perror("pthread_create");

23                 return -1;

24            }

25       }

26       while(1){**//Recover threads in a non blocking way. One thread is successfully recovered each time, and the variable increases automatically until all three threads are recovered**

27            for(i = 0;i <3;i++){

28                 if(pthread_tryjoin_np(tid[i],&Tmp) == 0){

29                     printf("Pthread : %d exit !\n",(int )(long )Tmp+1);

30                     flag++;   

31                 }

32            }

33            if(flag >= 3) break;

34       }

35       return 0;

36   }

37

Operation results:

Routine 7 shows how to reclaim threads in a non blocking way. In addition, it also shows that multiple threads can point to the same callback function. Routine 6 recovers threads by blocking, which almost specifies the sequence of thread recovery. If the first recovered thread does not exit, it will always be blocked, resulting in the failure of subsequent first exited threads to recover in time.

Through function pthread_tryjoin_np, using non blocking recycling, threads can recycle resources freely according to the exit order.

Test routine 8: (Phtread_txex8.c)

1    #define _GNU_SOURCE 

2    #include <pthread.h>

3    #include <stdio.h>

4    #include <unistd.h>

5    #include <errno.h>

6 

7    void *fun1(void *arg)

8    {

9        printf("Pthread:1 come!\n");

10       while(1){

11            sleep(1);

12       }

13   }

14

15   void *fun2(void *arg)

16   {

17       printf("Pthread:2 come!\n");

18       **pthread_cancel((pthread_t )(long)arg);//Kill thread 1 and force it to exit**

19       pthread_exit(NULL);

20   }

21

22   int main()

23   {

24       int ret,i,flag = 0;

25       void *Tmp = NULL;

26       pthread_t tid[2];

27       ret = pthread_create(&tid[0],NULL,fun1,NULL);

28       if(ret != 0){

29            perror("pthread_create");

30            return -1;

31       }

32       **sleep(1);**

33       ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);//Thread number of transport thread 1

34       if(ret != 0){

35            perror("pthread_create");

36            return -1;

37       }

38       while(1){**//Recover threads in a non blocking way. One thread is successfully recovered each time, and the variable increases automatically until all two threads are recovered**

 

39            for(i = 0;i <2;i++){

40                 if(pthread_tryjoin_np(tid[i],NULL) == 0){

41                     printf("Pthread : %d exit !\n",i+1);

42                     **flag++;**   

43                 }

44            }

45            if(flag >= 2) break;

46       }

47       return 0;

48   }

49

Operation results:

Routine 8 shows how to use pthread_ The cancel function actively ends a thread. Lines 27 and 33 create threads and pass the thread number of the first thread to the second thread in the form of parameter transfer. The first thread executes the dead loop sleep logic. Theoretically, it will never end unless the process ends, but pthread is called in the second thread_ The cancel function is equivalent to sending an exit instruction to the thread, resulting in the thread being exited and finally the resources being non blocked. This routine should pay attention to the sleep function on line 32 and ensure that thread 1 executes first. Because the thread executes disorderly, this sleep function is added to control the sequence. Later in this chapter, we will explain how to reasonably control the critical resource access of the thread and the sequence control of thread execution through locking, semaphore and other means.

2 thread control

2.1 multithreading critical resource access

When threads operate public resources, such as global variables, during operation, they may "conflict" with each other. For example, thread 1 attempts to increase the variable by itself, while thread 2 attempts to decrease the variable by itself. There is a competitive relationship between the two threads, resulting in the variable always in a "balanced state". The two threads compete with each other. Thread 1 increases the variable by itself after obtaining the execution right, and decreases the variable by itself after thread 2 obtains the execution right. The variable always seems to float within a certain range and cannot reach the expected value, as shown in routine 9.

Test routine 9: (Phtread_txex9.c)

1    #define _GNU_SOURCE 

2    #include <pthread.h>

3    #include <stdio.h>

4    #include <unistd.h>

5    #include <errno.h>

6 

7 

8    int Num = 0;

9 

10   void *fun1(void *arg)

11   {

12       while(Num < 3){

13            Num++;

14            printf("%s:Num = %d\n",__FUNCTION__,Num);

15            sleep(1);

16       }

17       pthread_exit(NULL);

18   }

19

20   void *fun2(void *arg)

21   {

22       while(Num > -3){

23            Num--;

24            printf("%s:Num = %d\n",__FUNCTION__,Num);

25            sleep(1);

26       }

27       pthread_exit(NULL);

28   }

29

30   int main()

31   {

32       int ret;

33       pthread_t tid1,tid2;

34       ret = pthread_create(&tid1,NULL,fun1,NULL);

35       if(ret != 0){

36            perror("pthread_create");

37            return -1;

38       }

39       ret = pthread_create(&tid2,NULL,fun2,NULL);

40       if(ret != 0){

41            perror("pthread_create");

42            return -1;

43       }

44       pthread_join(tid1,NULL);

45       pthread_join(tid2,NULL);

46       return 0;

47   }

48

Operation results:

In order to solve the above problem of competing for critical resources, pthread thread introduces mutex to solve the access of critical resources. By locking the critical resources, the resources are protected. Only a single thread can operate the resources. After the operation is unlocked, the other threads can obtain the operation right.

2.2 brief description of mutex API

This function is used to initialize a mutex. Generally, it applies for a global pthread_ mutex_ The mutex variable of type T completes the initialization in the lock through this function. The first function passes in the address of the variable, and the second parameter is the attribute controlling the mutex, which is generally NULL. When the function succeeds, it will return 0, which means that the mutex is initialized successfully. Of course, you can also call macros to initialize mutexes quickly:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

Lock function and unlock function are lock and unlock functions respectively. You only need to pass in the initialized pthread_mutex_t mutex variable. 0 will be returned after success. When a thread obtains the execution right, it executes the lock function. Once the locking is successful, other threads will block when they encounter the lock function. Until the thread that obtains the resource executes the unlock function, the blocking mode of the thread that obtains the second execution right is flushed, and lock is also obtained, resulting in other threads also blocking until the execution of unlock is unlocked.

In particular, when lock is obtained, unlock must be executed after the logical processing is completed, otherwise deadlock will occur! As a result, other threads are always blocked and cannot be executed. When using mutex, pay special attention to pthread_cancel function to prevent deadlock!

This function is also a thread locking function, but it is in non blocking mode. Whether locking is successful is judged by the return value. The usage is consistent with the above blocking locking function.

This function is used to destroy the mutex. Pass in the address of the mutex to complete the destruction of the mutex and return 0 successfully.

Test routine 10: (Phtread_txex10.c)

1    #define _GNU_SOURCE 

2    #include <pthread.h>

3    #include <stdio.h>

4    #include <unistd.h>

5    #include <errno.h>

6 

7    **pthread_mutex_t mutex;//Mutex variables generally apply for global variables**

8 

9    int Num = 0;//Common critical variable

10

11   void *fun1(void *arg)

12   {

13       pthread_mutex_lock(&mutex);//If a thread obtains a lock, it will be blocked

14       while(Num < 3){

15            Num++;

16            printf("%s:Num = %d\n",__FUNCTION__,Num);

17            sleep(1);

18       }

19       pthread_mutex_unlock(&mutex);//Unlock

20       pthread_exit(NULL);//Thread exit pthread_join recycles resources

21   }

22

23   void *fun2(void *arg)

24   {

25       pthread_mutex_lock(&mutex);//If a thread obtains a lock, it will be blocked

26       while(Num > -3){

27            Num--;

28            printf("%s:Num = %d\n",__FUNCTION__,Num);

29            sleep(1);

30       }

31       pthread_mutex_unlock(&mutex);//Unlock

32       pthread_exit(NULL);//Thread exit pthread_join recycles resources

33   }

34

35   int main()

36   {

37       int ret;

38       pthread_t tid1,tid2;

39       ret = pthread_mutex_init(&mutex,NULL);//Initialize mutex

40       if(ret != 0){

41            perror("pthread_mutex_init");

42            return -1;

43       }

44       ret = pthread_create(&tid1,NULL,fun1,NULL);//Create thread 1

45       if(ret != 0){

46            perror("pthread_create");

47            return -1;

48       }

49       ret = pthread_create(&tid2,NULL,fun2,NULL);//Create thread 2

50       if(ret != 0){

51            perror("pthread_create");

52            return -1;

53       }

54       pthread_join(tid1,NULL);//Blocking recycle thread 1

55       pthread_join(tid2,NULL);//Blocking recycle thread 2

56       pthread_mutex_destroy(&mutex);//Destroy mutex

57       return 0;

58   }

59

Operation results:

By adding mutex lock, the above routine ensures that the critical variable is only controlled by a thread at a certain time, and realizes the control of critical resources. It should be noted that threads are locked inside and outside the loop. This routine performs the locking operation before entering the while cycle. For the unlocking operation after the cycle ends, if all the locking and unlocking are put into the while cycle, as a single core machine, the execution result is the same. When a multi-core machine executes code, the phenomenon of "lock grabbing" may occur, which depends on the implementation of the bottom layer of the operating system.

2.3 execution sequence control of multithreaded programming

It solves the access of critical resources, but it seems that the execution sequence of threads cannot be controlled. Because threads execute out of order, the previous method of sleep forced delay can barely control the execution sequence, but this method is often undesirable in the actual project situation. It can only solve the sequence of thread creation, and the execution sequence will not be controlled after creation. Therefore, the concept of semaphore is introduced, Resolve thread execution order.

Routine 11 will show the randomness of the execution of the thread.

Test routine 11: (Phtread_txex11.c)

1    #define _GNU_SOURCE 

2    #include <pthread.h>

3    #include <stdio.h>

4    #include <unistd.h>

5    #include <errno.h>

6 

7    void *fun1(void *arg)

8    {

9        printf("%s:Pthread Come!\n",__FUNCTION__);

10       pthread_exit(NULL);

11   }

12

13   void *fun2(void *arg)

14   {

15       printf("%s:Pthread Come!\n",__FUNCTION__);

16       pthread_exit(NULL);

17   }

18

19   void *fun3(void *arg)

20   {

21       printf("%s:Pthread Come!\n",__FUNCTION__);

22       pthread_exit(NULL);

23   }

24

25   int main()

26   {

27       int ret;

28       pthread_t tid1,tid2,tid3;

29       ret = pthread_create(&tid1,NULL,fun1,NULL);

30       if(ret != 0){

31            perror("pthread_create");

32            return -1;

33       }

34       ret = pthread_create(&tid2,NULL,fun2,NULL);

35       if(ret != 0){

36            perror("pthread_create");

37            return -1;

38       }

39       ret = pthread_create(&tid3,NULL,fun3,NULL);

40       if(ret != 0){

41            perror("pthread_create");

42            return -1;

43       }

44       pthread_join(tid1,NULL);

45       pthread_join(tid2,NULL);

46       pthread_join(tid3,NULL);

47       return 0;

48   }

49

Operation results:

Through the above routine, it can be found that the order of executing the function multiple times is disordered, and the competition between threads cannot be controlled. The thread order can be controlled by using semaphores.

2.4. Semaphore API description

This function can initialize a semaphore, and the first parameter is passed into sem_t type address. The second parameter is passed in. 0 represents thread control, otherwise it is process control. The third parameter represents the initial value of semaphore, 0 represents blocking and 1 represents running. After initializing the semaphore, if the execution is successful, it will return 0.


sem_ The wait function is used to detect whether the specified semaphore has resources available. If no resources are available, the wait will be blocked. If resources are available, the operation of "sem-1" will be automatically executed. The so-called "sem-1" is consistent with the value of the third parameter in the above initialization function. If it is successfully executed, it will return 0

sem_ The post function will release the resources of the specified semaphore and execute the "sem+1" operation.

The above two functions can complete the so-called PV operation, that is, the application and release of semaphores, and complete the control of thread execution sequence.

Like mutex lock, this function is a non blocking function that controls semaphore application resources. Its function is the same as SEM_ The only difference is that this function is non blocking.

This function is a semaphore destruction function. After execution, the requested semaphore can be destroyed.

Test routine 12: (Phtread_txex12.c)

1    #define _GNU_SOURCE 

2    #include <pthread.h>

3    #include <stdio.h>

4    #include <unistd.h>

5    #include <errno.h>

6    #include <semaphore.h>

7 

8    sem_t sem1,sem2,sem3;//Three semaphore variables applied

9 

10   void *fun1(void *arg)

11   {

12       sem_wait(&sem1);//**Because sem1 itself has resources, it is not blocked * * sem1-1 will be blocked next time after it is obtained

13       printf("%s:Pthread Come!\n",__FUNCTION__);

14       sem_post(&sem2);// Enable sem2 to obtain resources

15       pthread_exit(NULL);

16   }

17

18   void *fun2(void *arg)

19   {

20       sem_wait(&sem2);//No resources will be blocked when sem2 is initialized until the execution of 14 lines of code is not blocked. sem2-1 will be blocked next time

21       printf("%s:Pthread Come!\n",__FUNCTION__);

22       sem_post(&sem3);// Enable sem3 to obtain resources

23       pthread_exit(NULL);

24   }

25

26   void *fun3(void *arg)

27   {

28       sem_wait(&sem3);//No resources will be blocked when sem3 is initialized until the execution of 22 lines of code is not blocked. sem3-1 will be blocked next time

29       printf("%s:Pthread Come!\n",__FUNCTION__);

30       sem_post(&sem1);// Enable sem1 to obtain resources

31       pthread_exit(NULL);

32   }

33

34   int main()

35   {

36       int ret;

37       pthread_t tid1,tid2,tid3;

38       ret = sem_init(&sem1,0,**1**); //Initialize semaphore 1 and give it resources

39       if(ret < 0){

40            perror("sem_init");

41            return -1;

42       }

43       ret = sem_init(&sem2,0,0); //Initialize semaphore 2 to block

44       if(ret < 0){

45            perror("sem_init");

46            return -1;

47       }

48       ret = sem_init(&sem3,0,0); //Initialize signal 3 to block

49       if(ret < 0){

50            perror("sem_init");

51            return -1;

52       }

53       ret = pthread_create(&tid1,NULL,fun1,NULL);//Create thread 1

54       if(ret != 0){

55            perror("pthread_create");

56            return -1;

57       }

58       ret = pthread_create(&tid2,NULL,fun2,NULL);//Create thread 2

59       if(ret != 0){

60            perror("pthread_create");

61            return -1;

62       }

63       ret = pthread_create(&tid3,NULL,fun3,NULL);//Create thread 3

64       if(ret != 0){

65            perror("pthread_create");

66            return -1;

67       }

68       /*Reclaim thread resources*/

69       pthread_join(tid1,NULL);

70       pthread_join(tid2,NULL);

71       pthread_join(tid3,NULL);

72

73       /*Destroy semaphore*/

74       sem_destroy(&sem1);

75       sem_destroy(&sem2);

76       sem_destroy(&sem3);

77

78       return 0;

79   }

80

Operation results:

The routine adds semaphore control to make the execution sequence of threads controllable. When initializing semaphore, semaphore 1 is filled into resources so that it is not used by sem_ The wait function is blocked, and sem is used after the logic is executed_ Post function to fill in the resources to be executed. When executing function sem_ After the wait, the sem self subtraction operation will be executed, so that the next competition will be blocked until it passes sem_post is released.

The above routine makes it acquire resources by default when initializing semaphore 1 in line 38, and makes it have no resources when initializing semaphores 2 and 3 in line 43 and 48. Therefore, in the thread processing function, each thread passes sem_wait function to wait for resources and send blocking phenomenon. Since the initial value of semaphore 1 is resource, the logic of thread 1 can be executed first. Line 12 SEM to be executed_ The wait function will cause sem1-1, so that this thread will be blocked next time. Then execute to line 14 through SEM_ The post function enables the sem2 semaphore to obtain resources, thus breaking through the logic blocking the execution thread 2... And so on to complete the orderly control of the thread.

3 Summary

The creation process of multithreading is shown in the following figure. First, you need to create a thread. Once the thread is created, there will be competition between threads for execution, preempting time slices to execute thread logic. When creating a thread, the parameters can be passed in through the fourth parameter of the thread. When the thread exits, the outgoing parameters can also be recycled by the thread recycling function to obtain the outgoing parameters.

Figure 1 thread programming flow

When multiple threads appear, they will encounter the problem of operating critical public resources at the same time. When the thread operates public resources, it is necessary to protect and lock the thread to prevent it and the thread from changing the variable at the same time when the thread changes the variable. After the logic execution is completed, it will be unlocked again to make the remaining threads compete again. The creation process of mutex is shown in the following figure.

Figure 2 mutex programming flow

When multiple threads appear, they will encounter the problem of disorderly execution at the same time. Sometimes, it is necessary to limit the execution order of threads. Variable introduces semaphores to control the execution order of threads through PV operation, as shown in the figure below.

Fig. 3 semaphore programming flow

Tags: Linux thread

Posted by brash on Wed, 11 May 2022 02:57:23 +0300