线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量以及读写锁。

互斥锁(mutex)

互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源。可以保证以下三点:

  • 原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。
  • 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。
  • 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

从以上三点,我们看出可以用互斥量来保证对变量(关键的代码段)的排他性访问。

互斥锁常用函数

#include <pthread.h>//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//互斥锁静态赋值
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//阻塞加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//非阻塞加锁,成功则返回0,否则返回EBUSY
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!

互斥锁属性

#include <pthread.h>int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);//锁的范围:PTHREAD_PROCESS_PRIVATE(进程内),PTHREAD_PROCESS_SHARED(进程间)
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);//锁的类型
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol);
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr,int protocol);int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *restrict attr, int *restrict prioceiling);
int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *attr,int prioceiling);   

说明:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在使用pthread_create()创建线程,以及调用pthread_atfork()函数建立fork处理程序时,需要链接该库。在编译中要加 -lpthread参数。

条件变量(cond)

利用线程间共享的全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞

使用说明:

  • 动态初始化调用pthread_cond_init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER静态初始化;属性置为NULL

  • pthread_cond_wait与pthread_cond_timedwait,在使用前应用程序必须执行了加锁互斥量,两函数在调用时自动解锁互斥量,等待条件互斥量触发。这时线程挂起,不占用CPU,前者直到条件变量被触发,后者等待条件变量被触发或者超时(返回ETIMEOUT)。函数返回前,自动重新对互斥量自动加锁。

  • 互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争— —个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。

  • pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程,否则返回EBUSY

  • 条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁。pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。

代码实例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;struct node
{int n_number;struct node *n_next;
} *head = NULL;static void cleanup_handler(void *arg)
{printf("Cleanup handler of second thread./n");free(arg);(void)pthread_mutex_unlock(&mtx);
}static void *thread_func(void *arg)
{struct node *p = NULL;pthread_cleanup_push(cleanup_handler, p);while (1){//这个mutex主要是用来保证pthread_cond_wait的并发性pthread_mutex_lock(&mtx);while (head == NULL){//这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何//这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线//程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。//这个时候,应该让线程继续进入pthread_cond_wait// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,//然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立//而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源//用这个流程是比较清楚的pthread_cond_wait(&cond, &mtx);p = head;head = head->n_next;printf("Got %d from front of queue/n", p->n_number);free(p);}pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁}pthread_cleanup_pop(0);return 0;
}int main(int argc, char *argv[])
{pthread_t tid;int i;struct node *p;//子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而//不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大pthread_create(&tid, NULL, thread_func, NULL);sleep(1);for (i = 0; i < 10; i++){p = (struct node*)malloc(sizeof(struct node));p->n_number = i;pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,p->n_next = head;head = p;pthread_cond_signal(&cond);pthread_mutex_unlock(&mtx); //解锁sleep(1);}printf("thread 1 wanna end the line.So cancel thread 2./n");//关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出//线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。pthread_cancel(tid);pthread_join(tid, NULL);return 0;
}

线程取消点

一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

相关概念

线程取消的方法是向目标线程发送Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用 pthread_testcancel(),从而达到POSIX标准所要求的目标.

相关API

  1. 取消线程运行
    int pthread_cancel(pthread_t thread);发送终止信号给thread线程,成功则返回0,否则返回非0. 成功发送并不意味着thread会终止.

  2. 设置取消点
    如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。
    pthread_testcancel:

    • 设置取消点
    • 检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。
  3. 设置线程取消状态与类型

    int pthread_setcancelstate(int state, int *oldstate);
    int pthread_setcanceltype(int type, int *oldtype);

    pthread_setcancelstate设置线程对Cancel的反应,PTHREAD_CANCEL_ENABLE(default)与PTHREAD_CANCEL_DISABLE分别表示接受信号后设为CANCEL转态或者忽略CANCEL信号继续运行

    pthread_setcanceltype设置本线程取消动作的执行时机,PTHREAD_CANCEL_DEFFERED(default)和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出).

代码实例

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>#define THREAD_MAX 4pthread_mutex_t mutex;
pthread_t thread[THREAD_MAX];static int tries;
static int started;void print_it(int *arg)
{pthread_t tid;tid = pthread_self();printf("Thread %lx was canceled on its %d try.\n",tid,*arg);
}void *Search_Num(int arg)
{pthread_t tid;int num;int k=0,h=0,j;int ntries;tid = pthread_self();srand(arg);num = rand()&0xFFFFFF;printf("thread num %lx\n",tid);ntries = 0;//默认设置//pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);pthread_cleanup_push((void *)print_it,(void *)&ntries);while(1) {num = (num+1)&0xffffff;ntries++;if(arg == num){//只允许一个线程操作此处while(pthread_mutex_trylock(&mutex) == EBUSY) { //一个线程操作后其余线程进入次循环挂起,等待pthread_cancel函数发送cancel信号终止线程k++;if(k == 100) {printf("----------2busy2-----------\n");}pthread_testcancel();}tries = ntries;//pthread_mutex_unlock(&mutex);   //如果加上这句话,将会有好几个线程找到主函数中设定的值pidprintf("Thread %lx found the number!\n",tid);for(j = 0;j<THREAD_MAX;j++) {if(thread[j] != tid) {pthread_cancel(thread[j]);}}break;}if (ntries % 100 == 0) {h++;/*线程阻塞,其他线程争夺资源,或者是等待pthread_cancel函数发送cancel信号终止线程*/pthread_testcancel();/*这是为了弄明白pthread_testcancel函数的作用而设置的代码段*/if(h == 10000){h = 0;printf("----------thread num %lx-------------\n",tid);}}}pthread_cleanup_pop(0);return (void *)0;
}int main()
{int i,pid;pid = getpid(); //设置要查找的数pthread_mutex_init(&mutex,NULL);printf("Search the num of %d\n",pid);for (started = 0; started < THREAD_MAX; started++) {pthread_create(&thread[started],NULL,(void *)Search_Num,(void *)pid);}for (i = 0; i < THREAD_MAX; i++) {pthread_join(thread[i],NULL);}printf("It took %d tries ot find the number!\n",tries);return 0;
}

参考:

  • 多线程编程之线程取消

linux线程间同步(1)互斥锁与条件变量相关推荐

  1. Linux多线程编程---线程间同步(互斥锁、条件变量、信号量和读写锁)

    本篇博文转自http://zhangxiaoya.github.io/2015/05/15/multi-thread-of-c-program-language-on-linux/ Linux下提供了 ...

  2. 【Linux C 多线程编程】互斥锁与条件变量

    一.互斥锁 互斥量从本质上说就是一把锁, 提供对共享资源的保护访问. 1) 初始化: 在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化: 对于静态 ...

  3. [C/C++]_[初级]_[ 线程pthread学习之互斥锁和条件变量的应用 ]

    一.互斥锁 互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源. 需要的头文件:pthread.h 互斥锁标识符:pthread_mutex_t (1)互斥锁初始化: 函数原型 ...

  4. 非常精简的Linux线程池实现(一)——使用互斥锁和条件变量

    https://blog.csdn.net/kxcfzyk/article/details/31719687 线程池的含义跟它的名字一样,就是一个由许多线程组成的池子. 有了线程池,在程序中使用多线程 ...

  5. 同步和互斥的POSXI支持(互斥锁,条件变量,自旋锁)

    同步和互斥在多线程和多进程编程中是一个基本的需求,互相协作的多个进程和线程往往需要某种方式的同步和互斥.POSIX定义了一系列同步对象用于同步和互斥. 同步对象是内存中的变量属于进程中的资源,可以按照 ...

  6. 【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)

    上篇文章也蛮好,线程同步之条件变量与互斥锁的结合: http://www.cnblogs.com/charlesblc/p/6143397.html  现在有这篇文章: http://blog.csd ...

  7. Linux系统编程:使用mutex互斥锁和条件变量实现多个生成者和消费者模型

    实现代码 如题,使用mutex互斥锁和条件变量实现多个生成者和消费者模型. 直接上代码,需要线程中的互斥锁和条件变量的相关知识进行支撑.这里就不细说了呀,代码中有一定的注释. #include < ...

  8. 信号量,互斥锁,条件变量的联系与区别

    转自:http://blog.chinaunix.net/u3/108685/showart_2127853.html 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程, ...

  9. 【C++】多线程互斥锁、条件变量

    我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量和条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!! 先来看下面这段简单的代码: int g_num = 0 ...

最新文章

  1. iOS将数字转成货币格式字符串
  2. 利用JSON-schema校验请求报文,封装转换错误信息,提示前台
  3. java加载证书,访问12306的https链接
  4. android栈式存储,线性表数据结构解读(三)栈结构Stack
  5. IOS 打包证书签名 shell脚本
  6. JDBC查询Oracle全部表名称,如何使用JDBC API从Oracle数据库中的现有表中检索记录?...
  7. Linux基本操作指南
  8. Something about TFS
  9. 论文笔记_S2D.55_2019_SLAM综述_Huang B. A Survey of Simultaneous Localization and Mapping
  10. 基于VM10+Win7安装Mac OSX10.11 El Capitan
  11. ASP.NET在Web窗体上输出九九乘法表
  12. 【汽车电子】嵌入式软件开发常用工具
  13. Python的安装以及Pycharm的安装配置【保姆级】
  14. 铁汁!高并发这些东西都是虚拟的,你都理解透彻了嘛?(高并发目标/高并发构架演进/分布式/面向服务架构/高并发平台)
  15. 怎么搭配丝袜优雅不低俗?
  16. 灵飞经3 印神无双 第十四章 印神古墓 3
  17. Android获取OAID
  18. 2022年全球与中国激光预警系统市场现状及未来发展趋势
  19. 名编辑电子杂志大师教程 | 添加购物车
  20. 【硬十宝典】——1.2【基础知识】开关电源各种拓扑结构的特点

热门文章

  1. 小心使用tf.image.resize_images,填坑经验分享给你
  2. [运维]---linux机器一般监控用到的概念记录
  3. iOS简单动画实现方案
  4. java grpc 客户端处理 go 服务端多返回值_grpc基础实践(二)
  5. steam成就解锁器_MC技术指南如何使用SAM成就解锁?
  6. python win10 连接hive_使用win10+python3.5+impyla 连接大数据平台hive表的步骤与问题解决...
  7. Java面向对象和面向过程有什么区别?网友:傻傻分不清楚……
  8. mysql分布式如何实现原理_分布式通讯协议实现原理
  9. python c java_简单明了看懂JAVA,Python和C+的优劣势
  10. android 流量统计工具,Android 统计应用流量的使用情况