Linux 多线程常用函数笔记
线程函数使用
【TID的类型: pthread_t】
- pthread_t是一个结构体数据类型,所以可移植操作系统实现不能把它作为整数处理
- Linux 3.2.0使用无符号长整型表示pthread_t数据类型
【线程TID的比较:pthread_equal】
#include <pthread.h> int pthread_equal(pthread_t tid1, pthread_t tid2); // 若相等,返回非0数值;否则,返回0
【获得自身的线程ID:pthread_self】
- 使用printf打印时,使用“%lu”打印pthread_t类型的值
#include <pthread.h> pthread_t pthread_self(void); // 返回调用线程的线程ID
【gettid()获取线程的tid】
- 函数gettid()可以得到线程的tid,但glibc并没有实现该函数,只能通过Linux的系统调用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); }
- 注:
- 使用上面的方法可以验证:主线程PID与进程PID相同
- 当进程中没有创建线程时,主进程也就相当于一个主线程,因为进程和线程的ID都是相同的
【线程创建函数: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函数传递的参数有一个以上,需要把这些参数放到一个结构中,传递该结构的地址 );
- 返回值:
- 成功:返回0
- 出错:返回错误编号,并不设置errno
- 案例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); }
- 案例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); }
【线程属性结构体:pthread_attr_t】
- 此结构的内容包含的就是线程的属性值,pthread_create的参数2就是使用此结构为线程设置初始属性值。
【线程属性结构体的创建与销毁】
int pthread_attr_init(pthread_attr_t *attr);
- 功能:用来对线程属性值进行初始化
- 注意:调用此函数之后,属性结构体的属性都是系统默认值,如果想要设置其他属性,还需要调用不同的函数进行设置
int pthread_attr_destroy(pthread_attr_t *attr);
- 功能:用来反初始化(销毁)属性。如果pthread_attr_init函数初始化的线程属性是动态分配的,那么此函数就会释放线程属性的内存空间
- pthread_attr_destory函数会用无效的值初始化属性对象,因此用此函数反初始化属性之后,属性就不能供pthread_create函数使用了.
【线程的终止方式】
- 下面这三种方法是正常不终止整个进程的情况下,终止线程并且停止它的控制流:
- ①线程可以简单地从启动例程中返回,返回值是线程的退出码
- ②线程可以被同一进程中的其他线程取消(pthread_cancel)
- ③线程调用pthread_exit
【线程退出函数:pthread_exit】
#include <pthread.h> void pthread_exit(void *rval_ptr);
- 功能:线程调用此函数终止自己
- 参数:rval_ptr是一个无类型指针,退出进程可以在这个指针里面设置内容(常用来设置终止码)。
- 进程中的其他线程也可以通过调用pthread_join函数访问这个指针
#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); }
【线程等待函数pthread_join】
#include <pthread.h> int pthread_join(pthread_t thread, void **rval_ptr);
- 返回值:成功返回0;失败返回错误编号
- 功能:用来等待参数1指定的线程结束
- 此函数会阻塞,直到指定的线程调用pthread_exit、从启动线程中返回、或者被取消,此函数才返回
- 参数:
- 参数1:指定等待的线程的ID
- 参数2:
- 填NULL:获取等待线程的终止信息,如果对线程的终止信息并不敢兴趣,可以设置为NULL。
- 非空:如果线程简单地从它的返回例程中返回,rval_ptr 就包含返回码。
- 如果线程被取消,由rval_ptr指定的内存单元就被设置为PTHREAD_CANCELED
- thread_join函数的使用与线程分离的关系:
- 调用pthread_join等待某一线程,被等待的线程结束之后就会被置于分离状态,这样线程所使用的资源就可以恢复
- 如果调用pthread_join等待一个线程时,如果线程已处于分离状态(例如调用pthread_detach函数),pthread_join调用会失败,返回EINVAL,尽管这种行为是与具体实现相关的
【线程取消函数:pthread_cancel】
#include <pthread.h> int pthread_cancel(pthread_t tid);
- 返回值:成功返回0;否则返回错误编号
- 功能:线程可以通过pthread_cancel来请求取消同一进程中的其它线程
- pthread_cancel并不等待线程终止,它仅仅提出请求
- 参数:
- 需要取消的线程ID
- 注意事项:
- 默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是被取消的线程可以选择忽略取消或者控制如何被取消
【线程取消点】
- 概念:系统自定义了一些线程取消点。当一个线程接收到其他线程的取消请求时,如果还没有运行到取消点,该线程还是会继续运行,直到运行到某个取消点,线程才真正地被取消线程取消点技术也称为“推迟取消”
#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); }
【线程的分离:pthread_detach】
- 如果线程未分离:线程的终止状态会一直保存,直到另外一个线程对该线程调用pthread_join获取其终止状态,终止状态才会被释放
- 如果线程已经被分离:线程的底层存储资源可以在线程终止时立即被回收
- 在线程被分离后,我们不能用pthread_join函数等待它的终止状态,因为对分离状态的线程调用pthread_join会产生未定义行为
#include <pthread.h> int pthread_detach(pthread_t tid);
- 返回值:成功返回0;否则返回错误编号
- 功能:用来分离线程
- 参数:要分离的线程tid
【互斥量:pthread_mutex_t】
- 互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量
- 互斥变量的初始化与释放
- ①静态初始化
- 直接把pthread_mutex_t互斥变量设置为常量PTHREAD_MUTEX_INITIALIZER
- 静态初始化互斥变量只能拥有默认的互斥量属性,不能设置其他互斥量属性
pthread_mutex_t mutex; mutex=PTHREAD_MUTEX_INITIALIZER; //或者 pthread_mutex_t *mutex=(pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); *mutex=PTHREAD_MUTEX_INITIALIZER;
- ②动态初始化
- 静态初始化互斥变量只能拥有默认互斥量属性,我们可以通过pthread_mutex_init函数来动态初始化互斥量,并且可以在初始化时选择设置互斥量的属性
#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);
- 返回值:成功返回0,否则返回错误编号
- pthread_mutex_init:
- 功能:对互斥量进行初始化
- 参数:
- 参数1: 需要初始化互斥量
- 参数2:初始化时互斥量的属性。如果使用默认属性,此处填NULL
- pthread_mutex_destroy:
- 功能:互斥量的反初始化
- 参数:互斥量
- 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数
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);
【互斥量的加锁与解锁】
#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);
- 对互斥量进行尝试加锁(非阻塞)。如果互斥量处于未加锁状态,那么pthead_mutex_trylock就会锁住这个互斥量;如果此锁处于加锁状态,那么pthead_mutex_trylock就出错返回EBUSY,并且不会阻塞
【引用计数案例】
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); } }
【超时互斥锁:pthread_mutex_timedlock】
#include <pthread.h> #include <time.h> int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
- 返回值:成功返回0,否则返回错误编号.
- 功能:当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock允许绑定线程阻塞时间
- 参数:
- mutex:尝试要加锁的互斥量
- tsptr:设置的超时时间
- 特点:在达到超时时间值时,如果还不能对互斥量成功加锁,那么就返回错误码ETIMEDOUT
- 超时时间的注意:超时指定愿意等待的绝对时间。这个时间值是一个绝对数而不是相对数。
- 例如,假设愿意等待3分钟,不是把3分钟转换为timespec结构体传递给参数2,而是需要把当前时间加上3分钟再转换成timespec结构然后传递给参数2
【pthread_mutex_timedlock避免永久阻塞案例】
#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); }
- 这个程序故意对它已有的互斥量进行加锁,目的是演示pthread_mutex_timedlock是如何工作的。不推荐在实际中使用这种方法,因为会导致死锁
- 注意:阻塞的时间可能会有所不同,造成不同的原因有多种:开始时间可能在某秒的中间位置,系统时钟的精度可能不足以精确到支持我们指定的超时时间值,或者在程序继续运行前,调度延迟可能会增加时间值
【条件变量:pthread_cond_t】
- 条件变量是线程可用的另一种同步机制
- 条件变量给多个线程提供了一个会合的场所
- 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
- 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样
- 条件变量要与互斥量一起使用,条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量
- 其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件
- ①静态初始化
- 直接把pthread_cond_t定义的条件变量设置为常量PTHREAD_COND_INITIALIZER
- 静态初始化条件变量只能拥有默认的条件变量属性,不能设置其他条件变量属性
pthread_cond_t cond; cond=PTHREAD_COND_INITIALIZER; //或者 pthread_cond_t *cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t)); *cond=PTHREAD_COND_INITIALIZER;
- ②动态初始化
- 静态初始化条件变量只能拥有默认条件变量属性,我们可以通过pthread_mutex_init函数来动态初始化条件变量,并且可以在初始化时选择设置条件变量的属性
#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);
- 返回值:成功返回0;失败返回错误编号
- pthread_cond_init:
- 功能:对条件变量初始化
- 参数:
- 参数1: 需要初始化的条件变量
- 参数2:初始化时条件变量的属性。如果使用默认属性,此处填NULL
- pthread_cond_destroy:
- 功能:对条件变量反初始化(在条件变量释放内存之前)
- 参数:条件变量
- 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数
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);
【等待条件变量函数】
#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);
- 返回值:成功返回0;失败返回错误编号
- 这两个函数调用成功返回时,线程需要重新计算条件,因为另一个线程可能已经在运行并改变了条件
- pthread_cond_wait
- 注意:等待条件变量变为真
- 如何使用:参数mutex互斥量提前锁定,然后该互斥量对条件进行保护,等待参数1cond条件变量变为真。在等待条件变量变为真的过程中,此函数一直处于阻塞状态。但是处于阻塞状态的时候,mutex互斥量被解锁(因为其他线程需要使用到这个锁来使条件变量变为真)
- 当pthread_cond_wait函数返回时,互斥量再次被锁住
- pthread_cond_timedwait
- pthread_cond_timedwait函数与pthread_cond_wait函数功能相同。不过多了一个超时参数。超时值指定了我们愿意等待多长时间,它是通过timespec结构体表示的
- 如果超时到期之后,条件还是没有出现,此函数将重新获取互斥量,然后返回错误ETIMEOUT
- 注意:这个时间值是一个绝对数而不是相对数。例如,假设愿意等待3分钟,那么不是把3分钟转换为timespec结构体,而是需要把当前时间加上3分钟再转换成timespec结构
【得到超时值的绝对时间获取函数】
- 可以使用clock_gettime函数获取timespec结构表示的当前时间。但是目前并不是所有的平台都支持这个函数。因此,可以使用gettimeofday获取timeval结构表示的当前时间,然后把这个时间转换为timespec结构
#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; }
【条件变量信号发送函数】
#include <pthread.h> int pthread_cond_signal(pthread_cond_t* cond); //至少能唤醒一个等待该条件的线程 int pthread_cond_broadcast(pthread_cond_t* cond); //唤醒等待该条件的所有线程
- 返回值:成功返回0;失败返回错误编号
- 这两个函数用于通知线程条件变量已经满足条件(变为真)。在调用这两个函数时,是在给线程或者条件发信号
- 必须注意:一定要在改变条件状态以后再给线程发信号
【结合使用条件变量和互斥量对线程进行同步】
#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); }