This article describes the basic use of threads on Linux.

1. Preface

Difference between threads and processes (1) Process: It is the smallest unit of OS scheduling. ps, top, etc. commands can be used to view the details of processes under Linux. (2) Thread: It is the smallest unit of process scheduling, each process has a main thread. The main thing to do in the process is the thread.

(3) In the whole system, the process ID is the unique identifier, and the management of processes is done by PID. When a process is created, a structure is created in the kernel to store all the information about the process, and each node that stores the process information also stores its own PID, which is used to manage the process when it is needed (e.g., sending signals). When a child process ends and needs to be recycled (when it calls exit() to exit or when code execution is finished), it needs to be done with the wait() system call, and the unrecovered extinct process becomes a zombie process that no longer exists as a process entity, but takes up PID resources, so recycling is necessary.

For threads, to actively terminate you need to call pthread_exit() and the main thread needs to call pthread_join() to recycle (provided the thread does not have the “detach property” set). Signaling a thread like a thread sends a thread signal is also implemented via the thread ID.

Communication methods between processes: A. Shared memory B. Message queues C. Semaphore D. Named pipes E. Nameless pipes F. Signals G. Files H. Sockets Communication methods between threads: A. Mutual exclusion B. Spin locks C. Conditional variables D. Read/write locks E. Thread signals F. Global variables

The communication method used between processes either requires switching the kernel context or accessing with peripherals (famous pipes, files). So it will be slower. If threads use their own specific communication method, it is basically done in their own process space, there is no switching, so the communication speed will be faster. In other words, there is a difference in speed between the communication methods used by processes and threads, in addition to the difference in type.

Note: When a process running multiple threads catches a signal, it will only block the main thread, while other sub-threads will not be affected and will continue to execute.

2.1 Creating threads

pthread_create is a function for creating threads for Unix operating systems (Unix, Linux, etc.). Compile time requires specifying the link library: -lpthread function prototype.

1
2
3
4
5
6
7
8
#include <pthread.h>
int pthread_create
(
    pthread_t *thread, 
    const pthread_attr_t *attr,
    void *(*start_routine) (void *), 
    void *arg
);

Parameter introduction

The first parameter is a pointer to the thread identifier. The second parameter is used to set the thread property. The third parameter is the starting address of the thread running function. The last parameter is the argument of the run function. The last argument is the argument to run the function. NULL is not required. see function help under Linux: # man pthread_create

man pthread_create

Return Value: Returns 0 if the thread creation was successful, or the error number if the thread creation failed. After a successful thread creation, the attr parameter is used to specify various different thread properties. The newly created thread starts at the address of the start_rtn function, which has only one universal pointer argument arg. If more than one argument needs to be passed to the thread work function, then these arguments need to be put into a structure, and the address of this structure is passed in as an argument to arg.

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <pthread.h>

//线程函数1
void *pthread_func1(void *arg)
{
  while(1)
  {
    printf("线程函数1正在运行.....\n");
    sleep(2);
  }
}

//线程函数2
void *pthread_func2(void *arg)
{
  while(1)
  {
    printf("线程函数2正在运行.....\n");
    sleep(2);
  }
}

int main(int argc,char **argv)
{
  
  pthread_t thread_id1;
  pthread_t thread_id2;
   /*1. 创建线程1*/
    if(pthread_create(&thread_id1,NULL,pthread_func1,NULL))
  {
    printf("线程1创建失败!\n");
    return -1;
  }
  /*2. 创建线程2*/
    if(pthread_create(&thread_id2,NULL,pthread_func2,NULL))
  {
    printf("线程2创建失败!\n");
    return -1;
  }
  
  /*3. 等待线程结束,释放线程的资源*/
  pthread_join(thread_id1,NULL);
  pthread_join(thread_id2,NULL);
  return 0;
}

//gcc pthread_demo_code.c -lpthread

2.2 Exiting a thread

A thread terminates execution by calling the pthread_exit function in the same way that a process calls the exit function at the end. This function terminates the thread that called it and returns a pointer to an object.

This function terminates the thread that called it and returns a pointer to an object, the return value of which can be obtained from the second argument of the pthread_join function.

Prototype function

1
2
#include <pthread.h>
void pthread_exit(void *retval);

Parameter resolution The address of the thread to return to. Note: The thread stack must be freed at the end of the thread, that is, the thread function must call pthread_exit() to end it, otherwise it is not freed until the main process function exits.

2.3 Waiting for a thread to finish

The pthread_join() function waits in a blocking fashion for the thread specified by thread to finish. When the function returns, the resources of the thread being waited for are retrieved. If the thread has already finished, then the function returns immediately. The thread specified by thread must have the joinable property. Prototype function.

1
2
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

Parameters First parameter: thread identifier, i.e. thread ID, which identifies a unique thread. Last parameter: user-defined pointer to store the address of the thread being waited for. Return value 0 means success. For failure, an error number is returned. Example of receiving a thread return value:

1
2
3
4
5
//退出线程
pthread_exit ("线程已正常退出");
//接收线程的返回值
void *pth_join_ret1;
pthread_join( thread1, &pth_join_ret1);

2.4 Thread separation property

The default state for creating a thread is joinable (join property). If a thread finishes running without calling pthread_join, its state is similar to that of a Zombie Process in a process, i.e. there are still some resources that have not been recovered (exit status code), so the person creating the thread should pthread_join to wait for the thread to finish running and get the exit code of the thread to recover its resources (similar to wait,waitpid of a process). But after calling the pthread_join(pthread_id) function, if the thread does not finish running, the caller will be blocked, which in some cases we don’t want.

The pthread_detach function sets the state of the thread to detached, which automatically releases all resources when the thread finishes running. Prototype function

1
2
#include <pthread.h>
int pthread_detach(pthread_t thread);

Parameters Thread identifier Return value 0 means success. Error returns an error code. EINVAL thread is not a joinable thread. esrch has no thread ID to be found.

2.5 Getting the identifier of the current thread

The pthread_self function gets the ID of the thread itself. Prototype function

1
2
#include <pthread.h>
pthread_t pthread_self(void);

return value The identifier of the current thread. pthread_t is of type unsigned long int, so use the %lu method when printing, otherwise there will be problems displaying the results.

2.6 Automatic thread resource cleanup

A thread can schedule functions to be called when it exits. Such functions are called thread cleanup handlers. This is used to do some post-exit resource cleanup when the program exits abnormally. A pthread_cleanup_push()/pthread_cleanup_pop() function is provided in the POSIX thread API to automatically release resources. Termination actions (including calls to pthread_exit() and exception termination) in the segment between the point of call of pthread_cleanup_push() and pthread_cleanup_pop() will execute the cleanup function specified by pthread_cleanup_push().

Note: The pthread_cleanup_push function needs to be called in pairs with the pthread_cleanup_pop function. function prototype

1
2
void pthread_cleanup_push(void (*routine)(void *),void *arg); //注册清理函数
void pthread_cleanup_pop(int execute); //释放清理函数

parameters void (*routine)(void *) : The function entry for the handler. void *arg : The formal parameter passed to the handler. int execute : The status value of execute. 0 indicates that the cleanup function is not called. 1 indicates that the cleanup function is called.

Conditions that cause the cleanup function to be called:

  1. pthread_exit() function is called
  2. pthread_cleanup_pop has a formal reference of 1. Note: return does not result in a cleanup function call.

2.7 Auto-cleanup thread sample code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

//线程清理函数
void routine_func(void *arg)
{
   printf("线程资源清理成功\n");
}
  
//线程工作函数
void *start_routine(void *dev)
{
   pthread_cleanup_push(routine_func,NULL);

  //终止线程
  // pthread_exit(NULL);
   
   pthread_cleanup_pop(1); //1会导致清理函数被调用。0不会调用。

}

int main(int argc,char *argv[])
{
   pthread_t thread_id;  //存放线程的标识符
  
  /*1. 创建线程*/
  if(pthread_create(&thread_id,NULL,start_routine,NULL)!=0)
  {
     printf("线程创建失败!\n");    
  } 
  /*2.设置线程的分离属性*/
  if(pthread_detach(thread_id)!=0)
  {
     printf("分离属性设置失败!\n");
  }
  while(1){}  
  return 0;  
}

2.8 Thread cancellation functions

The pthread_cancel function is a thread cancellation function, which is used to cancel other threads in the same process.

1
2
头文件: #include <pthread.h>
函数原型:pthread_cancel(pthread_t tid);