Summary of common knowledge points of Linux multithreading

Linux multithreading commonly used function notes

Thread function usage

[Type of TID: pthread_t]

  • pthread_t is a structured data type, so portable operating system implementations cannot treat it as an integer
  • Linux 3.2.0 uses an unsigned long integer to represent the pthread_t data type

[Comparison of thread TID: pthread_equal]

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 若相等,返回非0数值;否则,返回0

[Get its own thread ID: pthread_self]

  • When printing with printf, use "%lu" to print the value of pthread_t type
#include <pthread.h>
pthread_t pthread_self(void); 
// 返回调用线程的线程ID

[Gettid() to get the tid of the thread]

  • The function gettid() can get the tid of the thread, but glibc does not implement this function, it can only be obtained through the Linux system call syscall (<sys/syscall.h>)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h> 

void *hello()
{
	printf("child,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
}

int main(int argc,char *agrv[])
{
	pthread_t thread_id;
	pthread_create(&thread_id,NULL,(void *)*hello,NULL);
	
	printf("parent,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
	pthread_join(thread_id,NULL);
}
  • Note:
    • Use the above method to verify: the main thread PID is the same as the process PID
    • When no thread is created in the process, the main process is equivalent to a main thread, because the ID of the process and the thread are the same

[Thread creation function: pthread_create]

#include <pthread.h>
int pthread_create(
	pthread_t              *restrict tidp,        //当pthread_create成功返回时,新创建的线程ID会被设置到tidp所指向的内存单元中
	const pthread_attr_t   *restrict attr,        //atrr参数用于指定线程创建时的初始化属性。值为NULL时,创建一个具有默认属性的线程。
	void                   *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行。该函数只有一个无类型指针参数arg,返回值为void*类型
	void                   *restrict  arg         //如果需要向start_rtn函数传递的参数有一个以上,需要把这些参数放到一个结构中,传递该结构的地址
	);
  • return value:
    • Success: return 0
    • Error: return the error number without setting errno
  • Case 1
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
pthread_t ntid;
 
void printids(const char *s){
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,(unsigned long)tid, 
    (unsigned long)tid);
}
 
void *thr_fn(void *arg){
    printids("new thread: ");
    return((void *)0);
}
 
int main(void)
{
    int err;
    //create thread
    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err != 0)
        printf("can’t create thread\n");
    printids("main thread:");
    sleep(1);
    exit(0);
}
  • Case 2
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h> 

struct message
{
	int i;
	int j;
};

void *hello(struct message *str)
{
	printf("child,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
	printf("the arg.i is %d,arg.j is %d\n",str->i,str->j);
}
 
int main(int argc,char *agrv[])
{
	struct message test;
	pthread_t thread_id;
	test.i=10;
	test.j=20;
	pthread_create(&thread_id, NULL,(void *)*hello,&test);
	printf("parent,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid));
	pthread_join(thread_id,NULL);
}

[Thread attribute structure: pthread_attr_t]

  • The content of this structure contains the attribute value of the thread, and the parameter 2 of pthread_create uses this structure to set the initial attribute value for the thread.

[Creation and destruction of thread attribute structure]

int pthread_attr_init(pthread_attr_t *attr);
  • Function: used to initialize the thread attribute value
  • Note: After calling this function, the attributes of the attribute structure are system default values. If you want to set other attributes, you need to call different functions to set them.
int pthread_attr_destroy(pthread_attr_t *attr);
  • Function: used to de-initialize (destroy) attributes. If the thread attributes initialized by the pthread_attr_init function are dynamically allocated, then this function will release the memory space of the thread attributes
  • The pthread_attr_destory function initializes the attribute object with an invalid value, so after deinitializing the attribute with this function, the attribute cannot be used by the pthread_create function.

[Thread termination method]

  • The following three methods are to terminate the thread and stop its control flow without terminating the entire process normally:
    • ① The thread can simply return from the startup routine, and the return value is the thread's exit code
    • ② Thread can be cancelled by other threads in the same process ( pthread_cancel )
    • ③Thread calls pthread_exit

[Thread exit function: pthread_exit]

#include <pthread.h>
void pthread_exit(void *rval_ptr);
  • Function: the thread calls this function to terminate itself
  • Parameter: rval_ptr is an untyped pointer, and the content can be set in this pointer when exiting the process (usually to set the termination code).
    • Other threads in the process can also access this pointer by calling the pthread_join function
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
void * thr_fn1(void *arg){
    printf("thread 1 returning\n");
    return((void *)1);
}
void *thr_fn2(void *arg){
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}
int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;
 
    err = pthread_create(&tid1, NULL, thr_fn1, NULL);//创建线程1
    if (err != 0)
        printf("can’t create thread 1\n");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);//创建线程2
    if (err != 0)
        printf("can’t create thread 2\n");
 
    err = pthread_join(tid1, &tret);//等待线程1
    if (err != 0)
        printf("can’t join with thread 1\n");
    printf("thread 1 exit code %ld\n", (long)tret);
    
    err = pthread_join(tid2, &tret);//等待线程2
    if (err != 0)
        printf("can’t join with thread 2\n");
    printf("thread 2 exit code %ld\n", (long)tret);
   
     exit(0);
}

[Thread wait function pthread_join]

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
  • Return value: return 0 on success; return error number on failure
  • Function: used to wait for the end of the thread specified by parameter 1
  • This function will block until the specified thread calls pthread_exit, returns from the starting thread, or is cancelled, then this function returns
  • parameter:
    • Parameter 1: Specify the ID of the waiting thread
    • Parameter 2:
      • Fill NULL: Get the termination information of the waiting thread. If you are not interested in the termination information of the thread, you can set it to NULL.
      • Non-empty: If the thread simply returns from its return routine, rval_ptr contains the return code.
      • If the thread is cancelled, the memory unit specified by rval_ptr is set to PTHREAD_CANCELED
  • The relationship between the use of thread_join function and thread separation:
    • Call pthread_join to wait for a thread. After the waiting thread ends, it will be placed in a detached state, so that the resources used by the thread can be restored
    • If pthread_join is called to wait for a thread, if the thread is already in a detached state (for example, the pthread_detach function is called), the pthread_join call will fail and return EINVAL, although this behavior is related to the specific implementation

[Thread cancellation function: pthread_cancel]

#include <pthread.h>
int pthread_cancel(pthread_t tid);
  • Return value: return 0 if successful; otherwise, return error number
  • Function: Thread can request cancellation of other threads in the same process through pthread_cancel
  • pthread_cancel does not wait for the thread to terminate, it just makes a request
  • parameter:
    • ID of the thread that needs to be cancelled
  • Precautions:
    • By default, the pthread_cancel function will make the thread identified by tid behave as if calling the pthread_exit function with the parameter PTHREAD_CANCELED, but the cancelled thread can choose to ignore the cancellation or control how it is cancelled

[Thread cancellation point]

  • Concept: The system customizes some thread cancellation points. When a thread receives a cancellation request from another thread, if it has not run to the cancellation point, the thread will continue to run until it runs to a certain cancellation point, the thread is actually canceled. The thread cancellation point technology is also called "postponement". cancel"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
 
void *thread_func(void* arg);
 
int main()
{
    pthread_t tid;
    int *thread_exit_code=malloc(sizeof(int));
    if(pthread_create(&tid,NULL,thread_func,NULL)!=0){
        fprintf(stdout,"pthread_create error\n");
        exit(EXIT_FAILURE);
    }
 
    //进程休眠1秒,然后取消子线程
    sleep(1);
    if(pthread_cancel(tid)!=0){
        fprintf(stdout,"pthread_cancel error\n");
        exit(EXIT_FAILURE);
    }
 
    printf("pthread_cancel filaed\n");
    
    //睡眠8秒之后取消线程失败了,因为线程已经退出了
    sleep(8);
    if(pthread_cancel(tid)!=0){
        fprintf(stdout,"pthread_cancel error\n");
        exit(EXIT_FAILURE);
    }
    
    printf("kill thread success\n");
 
    if(pthread_join(tid,(void*)&thread_exit_code)==0){
        printf("pthread_join success,exit_code is %d\n",(int)*thread_exit_code);
    }else{
        fprintf(stdout,"pthread_join error\n");
        exit(EXIT_FAILURE);
    }
 
    exit(0);
}
 
void *thread_func(void* arg)
{
    int exit_code,i;
    
    //进入之后,先设置自己为不可取消状态
    printf("I am thread,now my cancle type is disable\n");
    if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL)!=0){
        fprintf(stdout,"pthread_setcancelstate error\n");
        exit_code=-1;
        pthread_exit(&exit_code);
    }
   
 
    for(i=1;i<=3;i++){
        sleep(1);
        printf("thread running (%d)\n",i);
    }
 
    //休眠3秒之后之后设置线程可以被取消
    printf("I am thread,now my cancle type is enable\n");
    if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL)!=0){
        fprintf(stdout,"pthread_setcancelstate error\n");
        exit_code=-1;
        pthread_exit(&exit_code);
    }
    
    printf("thread sleep....\n");
    sleep(20);
 
    pthread_exit(NULL);
}

[Thread separation: pthread_detach]

  • If the thread is not separated: the termination status of the thread will be kept until another thread calls pthread_join on the thread to obtain its termination status, the termination status will not be released
  • If the thread has been detached: the underlying storage resources of the thread can be reclaimed immediately when the thread terminates
  • After the thread is detached, we cannot use the pthread_join function to wait for its termination state, because calling pthread_join on the thread in the detached state will produce undefined behavior
#include <pthread.h>
int pthread_detach(pthread_t tid);
  • Return value: return 0 if successful; otherwise, return error number 
  • Function: used to separate threads
  • Parameters: the tid of the thread to be detached

[Mutual exclusion: pthread_mutex_t]

  • The mutex is essentially a lock. The mutex is set (locked) before accessing shared resources, and the mutex is released (unlocked) after the access is completed
  • Initialization and release of mutually exclusive variables
  • ①Static initialization
    • Set the pthread_mutex_t mutex variable directly to the constant PTHREAD_MUTEX_INITIALIZER
    • Statically initialized mutex variables can only have the default mutex attributes, and cannot set other mutex attributes
pthread_mutex_t  mutex;
mutex=PTHREAD_MUTEX_INITIALIZER;
//或者
pthread_mutex_t *mutex=(pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
*mutex=PTHREAD_MUTEX_INITIALIZER;
  • ②Dynamic initialization
    • Statically initialized mutex variables can only have the default mutex attributes, we can dynamically initialize the mutex through the pthread_mutex_init function, and you can choose to set the mutex attributes during initialization
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • Return value: return 0 if successful, otherwise return an error number
  • pthread_mutex_init:
    • Function: initialize the mutex
    • parameter:
      • Parameter 1: The mutex needs to be initialized
      • Parameter 2: The properties of the mutex during initialization. If you use the default attributes, fill in NULL here
  • pthread_mutex_destroy:
    • Function: Deinitialization of the mutex
    • Parameters: Mutex
    • Remarks (emphasis): This function only deinitializes the mutex, and does not release the memory space. If the mutex is requested through a function such as malloc, then you need to call the pthread_mutex_destroy function before freeing the mutex
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL); 
/*do something*/
pthread_mutex_destroy(&mutex);

pthread_mutex_t* mutex=(pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex, NULL);
/*do something*/
pthread_mutex_destroy(mutex);
free(mutex);

[Locking and unlocking of mutex]

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);  //对互斥量进行加锁。如果互斥量已经上锁,调用线程将阻塞到互斥量被解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);  //对互斥量进行解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
  • Try to lock the mutex (non-blocking). If the mutex is in an unlocked state, then pthead_mutex_trylock will lock the mutex; if the lock is in a locked state, then pthead_mutex_trylock will return EBUSY in error and will not block

[Case of Reference Counting]

struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
};
 
struct foo *foo_alloc(int id)
{
    struct foo *fp;
 
    if ((fp == malloc(sizeof(struct foo))) != NULL) {
        fp->f_count = 1;
        fp->f_id = id;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            fp = NULL;
        }
    }
 
    return fp;
}
 
void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}
 
void foo_rele(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    if (--fp->f_count == 0) {
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destory(&fp->f_lock);
        free(fp);
    } else {
        pthread_mutex_unlock(&fp->f_lock);
    }
}

[Timeout mutex: pthread_mutex_timedlock]

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
  • Return value: return 0 if successful, otherwise return an error number.
  • Function: When a thread tries to acquire a locked mutex, pthread_mutex_timedlock allows binding thread blocking time
  • parameter:
    • mutex: the mutex to try to lock
    • tsptr: set timeout time
  • Features: When the timeout value is reached, if the mutex cannot be successfully locked, the error code ETIMEDOUT will be returned
  • Note on overtime: overtime specifies the absolute time that is willing to wait. This time value is an absolute number rather than a relative number.
  • For example, if you are willing to wait 3 minutes, instead of converting 3 minutes into a timespec structure and passing it to parameter 2, you need to add 3 minutes to the current time and then convert it into a timespec structure and then pass it to parameter 2.

[Pthread_mutex_timedlock avoids permanent blocking cases]

#include <pthread.h>
 
int main(void)
{
    int err;
    struct timespec tout;
    struct tm *tmp;
    char buf[64];
 
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&lock);
    printf("mutex is locked\n");
	
    clock_gettime(CLOCK_REALTIME, &tout);
	
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("current time is %s\n", buf);
	
    tout.tv_sec += 10; /* 10 seconds from now */
    
    /* caution: this could lead to deadlock */
    err = pthread_mutex_timedlock(&lock, &tout);
	
    clock_gettime(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("the time is now %s\n", buf);
	
    if (err == 0)
        printf("mutex locked again!\n");
    else
        printf("can’t lock mutex again: %s\n", strerror(err));
    exit(0);
}
  • This program deliberately locks its existing mutex in order to demonstrate how pthread_mutex_timedlock works. It is not recommended to use this method in practice, because it will lead to deadlock
  • Note: The blocking time may be different. There are many reasons for different reasons: the start time may be in the middle of a certain second, the accuracy of the system clock may not be accurate enough to support the timeout value specified by us, or the program continues Before running, scheduling delay may increase the time value

[Condition variable: pthread_cond_t]

  • Condition variables are another synchronization mechanism available to threads
  • Condition variables provide a meeting place for multiple threads
  • When condition variables and mutex are used together, threads are allowed to wait for specific conditions to occur in a non-competitive manner
  • Condition variables are things in the thread, that is, waiting for a certain condition to occur, just like a signal
  • The condition variable should be used with the mutex, and the condition itself is protected by the mutex. The thread must first lock the mutex before changing the condition state
  • Other threads will not be aware of this change before obtaining the mutex, because the mutex must be locked before the condition can be calculated
  • ①Static initialization
    • Directly set the condition variable defined by pthread_cond_t to the constant PTHREAD_COND_INITIALIZER
    • Statically initialized condition variables can only have default condition variable attributes, and cannot set other condition variable attributes
pthread_cond_t cond;
cond=PTHREAD_COND_INITIALIZER;
//或者
pthread_cond_t *cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t));
*cond=PTHREAD_COND_INITIALIZER;
  • ②Dynamic initialization
    • Statically initialized condition variables can only have default condition variable attributes, we can dynamically initialize the condition variables through the pthread_mutex_init function, and can choose to set the attributes of the condition variable during initialization
#include <pthread.h>
int pthread_cond_init(pthread_cond_t* restrict cond,const pthread_condattr_t* restrict attr);
int pthread_cond_destroy(pthread_cond_t* cond);
  • Return value: return 0 on success; return error number on failure
  • pthread_cond_init:
    • Function: Initialize the condition variable
    • parameter:
      • Parameter 1: The condition variable that needs to be initialized
      • Parameter 2: The attribute of the condition variable during initialization. If you use the default attributes, fill in NULL here
  • pthread_cond_destroy:
    • Function: Deinitialize the condition variable (before the condition variable releases the memory)
    • Parameters: condition variables
    • Remarks (emphasis): This function only deinitializes the mutex, and does not release the memory space. If the mutex is requested through a function such as malloc, then you need to call the pthread_mutex_destroy function before freeing the mutex
pthread_cond_t cond;
pthread_cond_init(&cond,NULL);
/*do something*/
pthread_cond_destroy(&cond);

pthread_cond_t * cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t));
pthread_cond_init(cond,NULL);
/*do something*/
pthread_cond_destroy(cond);
free(cond);

[Wait for condition variable function]

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t* restrict cond,pthread_mutex_t* restrict mutex);
int pthread_cond_timedwait(pthread_cond_t* cond,pthread_mutex_t* restrict mutex,const struct timespec* restrict tsptr);
  • Return value: return 0 on success; return error number on failure
  • When these two function calls return successfully, the thread needs to recalculate the condition, because another thread may already be running and change the condition
  • pthread_cond_wait
    • Note: Wait for the condition variable to become true
    • How to use: The parameter mutex mutex is locked in advance, and then the mutex protects the condition, waiting for the parameter 1cond condition variable to become true. In the process of waiting for the condition variable to become true, this function has been blocked. But when in the blocking state, the mutex mutex is unlocked (because other threads need to use this lock to make the condition variable true)
    • When the pthread_cond_wait function returns, the mutex is locked again
  • pthread_cond_timedwait
    • The pthread_cond_timedwait function has the same function as the pthread_cond_wait function. But there is one more timeout parameter. The timeout value specifies how long we are willing to wait, which is represented by the timespec structure
    • If the condition does not appear after the timeout expires, this function will retrieve the mutex and return the error ETIMEOUT
    • Note: This time value is an absolute number and not a relative number. For example, if you are willing to wait for 3 minutes, instead of converting 3 minutes into a timespec structure, you need to add 3 minutes to the current time and then convert it into a timespec structure

[Get the absolute time acquisition function of the timeout value]

  • You can use the clock_gettime function to get the current time represented by the timespec structure. But not all platforms currently support this function. Therefore, you can use gettimeofday to get the current time represented by the timeval structure, and then convert this time into a timespec structure
#include <sys/time.h>
#include <stdlib.h>
 
void maketimeout(struct timespec *tsp, long minutes)
{
	struct timeval now;
 
	/* get the current time */
	gettimeofday(&now, NULL);
	
	tsp->tv_sec = now.tv_sec;
	tsp->tv_nsec = now.tv_usec * 1000; /* usec to nsec */
	/* add the offset to get timeout value */
	tsp->tv_sec += minutes * 60;
}

【Condition variable signal sending function】

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);    //至少能唤醒一个等待该条件的线程
int pthread_cond_broadcast(pthread_cond_t* cond); //唤醒等待该条件的所有线程
  • Return value: return 0 on success; return error number on failure
  • These two functions are used to notify the thread that the condition variable has met the condition (becomes true). When these two functions are called, they are signaling the thread or condition
  • Attention must be paid: the thread must be signaled after changing the condition state

[Combine the use of condition variables and mutex to synchronize threads]

#include <pthread.h>
 
struct msg {
	struct msg *m_next;
	/* ... more stuff here ... */
};
 
struct msg  *workq;
pthread_cond_t  qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
 
void  process_msg(void)
{
	struct msg *mp;
 
	for (;;) {
		pthread_mutex_lock(&qlock);
		while (workq == NULL)
			pthread_cond_wait(&qready, &qlock);
		mp = workq;
		workq = mp->m_next;
		pthread_mutex_unlock(&qlock);
		/* now process the message mp */
	}
}

void  enqueue_msg(struct msg *mp)
{
	pthread_mutex_lock(&qlock);
	mp->m_next = workq;
	workq = mp;
	pthread_mutex_unlock(&qlock);
	pthread_cond_signal(&qready);
}

 

Guess you like

Origin blog.csdn.net/baidu_41388533/article/details/114851779