一文搞定Linux线程间通讯 / 线程同步方式-互斥锁、读写锁、自旋锁、信号量、条件变量、信号等等

这篇具有很好参考价值的文章主要介绍了一文搞定Linux线程间通讯 / 线程同步方式-互斥锁、读写锁、自旋锁、信号量、条件变量、信号等等。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

线程间通讯 / 线程同步方式

锁机制

互斥锁(Mutex)

读写锁(rwlock)

自旋锁(spin)

信号量机制(Semaphore)

条件变量机制

信号(Signal)


线程间通讯 / 线程同步方式

p.s 以下有很多段落是直接引用,没有使用 markdown 的 “引用” 格式,出处均已放出。

参考 / 引用:

  • 100ask。

  • linux基础——linux线程间通信及同步机制总结yexz的博客-CSDN博客linux 线程间通信。

  • pthread的互斥量和自旋锁zhaopengnju的博客-CSDN博客pthread 自旋锁、pthread_spin自旋锁gdut17的博客-CSDN博客pthread 自旋锁。

  • pthread-win32 semaphore信号量总结 - ayanmw - 博客园 (cnblogs.com)。

由于线程间共享进程变量资源,线程间的通信目的主要是用于线程同步(即约束多个线程的执行的顺序规则(信号量)或互斥规则(互斥锁)),所以线程没有像进程通信中的用于数据交换的通信机制。

互斥锁、条件变量和信号量的区别:

  • 互斥锁:互斥,一个线程占用了某个资源,那么其它的线程就无法访问,直到这个线程解锁,其它线程才可以访问。

  • 信号量:同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而且信号量有一个更加强大的功能,信号量可以用作为资源计数器,把信号量的值初始化为某个资源当前可用的数量,使用一个之后递减,归还一个之后递增。

  • 条件变量:同步,一个线程完成了某一个动作就通过条件变量发送信号告诉别的线程,别的线程再进行某些动作。条件变量必须和互斥锁配合使用。

锁机制适用于类似原子操作的情况,加锁后 快速的处理某一个临界区的资源,然后立马解锁,不适合长时间的加锁(更不好的情况就是在加锁后的临界区里被执行了中断,在中断里面又要阻塞的进行加锁,那么这时就发生死锁了,卡住了);而信号/信号量(和 条件变量)适合 长时间的 等待 信号/条件的发生,而且 在等待/阻塞 期间调用者是休眠的。

为了减少错误 和 复杂性,设计程序前应尽量考虑 不要在 中断中 使用 锁、信号 等之类的东西,中断程序里面设计的要 简洁、优雅。

锁机制
互斥锁(Mutex)

互斥锁 / 互斥量 用来防止多个线程 同时/并发的 访问某个临界资源。

互斥量本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量。对互斥量进行加锁以后,其他识图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。

如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程就会看到互斥量依然是锁着,只能再次阻塞等待它重新变成可用,这样,一次只有一个线程可以向前执行。

即 多个线程都要访问某个临界资源,比如某个全局变量时,需要互斥地访问:我访问时,你不能访问。

头文件:#include <pthread.h>

常用 API:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); // 初始化量
/* 该函数初始化一个互斥量,第一个参数是改互斥量指针,第二个参数为控制互斥量的属性,一般为 NULL。当函数成功后会返回 0,代表初始化互斥量成功。
    当然初始化互斥量也可以调用宏来快速初始化,代码如下:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
*/
​
int pthread_mutex_lock(pthread_mutex_t *mutex);                                       // 加锁(阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mutex);                                     // 解锁(非阻塞)
/* lock 函数与 unlock 函数分别为加锁解锁函数,只需要传入已经初始化好的 pthread_mutex_t 互斥量指针。成功后会返回 0。
    当某一个线程获得了执行权后,执行 lock 函数,一旦加锁成功后,其余线程遇到 lock 函数时候会发生阻塞,直至获取资源的线程执行 unlock 函数后。unlock 函数会唤醒其他正在等待互斥量的线程。
    特别注意的是,当获取 lock 之后,必须在逻辑处理结束后执行 unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。在使用互斥量的时候,尤其要注意使用 pthread_cancel 函数,防止发生死锁现象!
*/
​
int pthread_mutex_trylock(pthread_mutex_t *mutex);                                   // 互斥量加锁(非阻塞)
/* 该函数同样也是一个线程加锁函数,但该函数是非阻塞模式通过返回值来判断是否加锁成功,用法与上述阻塞加锁函数一致。 */
​
int pthread_mutex_destroy(pthread_mutex_t *mutex);                                    // 销毁互斥量
/* 该函数是用于销毁互斥量的,传入互斥量的指针,就可以完成互斥量的销毁,成功返回0。 */

例程:参考 pthread库-线程编程例程-来自百问网\01_文档配套源码\Pthread_Text10.c

互斥锁的属性:

/* 与 线程属性类似的,先 声明变量,再用 pthread_mutexattr_init 初始化,
    再用 pthread_mutexattr_getxxx/pthread_mutexattr_setxxx 来 获取 / 设置 属性的某个选项,
    然后在 调用 互斥锁初始化 pthread_mutex_init 的时候 填入 该属性
    最后可以销毁 */
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
​
int pthread_mutexattr_getshared(const pthread_mutexattr_t* attr, int* pshared);
int pthread_mutexattr_setshared(pthread_mutexattr_t* attr, int* pshared);
    pshared 的可以传入的参数:
        PTHREAD_PROCESS_SHARED:互斥锁可以被跨进程共享
        PTHREAD_PROCESS_PRIVATE:只能被初始化线程所属的进程中的线程共享
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type);
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);
    type 的可以传入的参数:
        PTHREAD_MUTEX_NOMAL:公平锁,对一个已经加锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果。
        PTHREAD_MUTEX_ERRORCHECK:检错锁,对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLOCK。对一个已经被其他线程加锁的检错锁解锁,或者对一个已经解锁的检错锁再次解锁,则解锁操作返回EPERM。
        PTHREAD_MUTEX_RECURSIVE:嵌套锁,错误使用返回EPERM
        PTHREAD_MUTEX_DEFAULT:跟nomal差不多。
读写锁(rwlock)

 读写锁与互斥量类似,不过读写锁拥有更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有 3 种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。

头文件:#include <pthread.h>

常用 API:

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlockattr); // 初始化读写锁
​
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);                                       // 读模式锁定读写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);                                       // 写模式锁定读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);                                       // 解锁读写锁
​
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);                                      // 销毁读写锁
自旋锁(spin)

如果 互斥锁 被占用(被锁住),另一个线程进入时,互斥锁会引起线程切换(不死等,而是 yield 一次去运行其它线程)。适合锁的内容比较多的。

对于自旋锁,如果锁被占用(被锁住),来了的线程会一直等待直到获取这把锁相当于 while(1);(死等,跑满一个时间片时间)。适合锁的内容比较少的 当线程切换的代价远远比等待的代价大的时候,使用自旋锁。(如果对于 RTOS 有了解的,就会明白 yeild、跑满一个时间片时间 等的概念,可以玩一玩 单片机上的 FreeRTOS、RTT 就了解了)。

自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。自旋锁可以用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。

如果一个可执行线程试图获得一个被争用(已经被持有的)自旋锁,那么该线程就会一直进行忙等待,自旋,也就是空转,等待锁重新可用(等待被解锁)。一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋,特别的浪费CPU时间,所以自旋锁不应该被长时间的持有。实际上,这就是自旋锁的设计初衷,在短时间内进行轻量级加锁。自旋锁 也不能用在 中断程序里面,自旋锁保持期间是抢占失效的(内核不允许被抢占)。

头文件:#include <pthread.h>

常用 API:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
/* 自旋锁 初始化函数,里面没有 属性变量,pshared 可以选择的参数:
    PTHREAD_PROCESS_PRIVATE 只在本进程内 使用该锁
    PTHREAD_PROCESS_SHARED 该锁可能位于共享内存对象中,在多个进程之间共享
    如果想要使用自旋锁同步多进程,那么设置 pshared 为 PTHREAD_PROCESS_SHARED,然后在进程共享内存中分配 pthread_spinlock_t 对象即可(pthread_mutex_t 亦如此)。
*/
​
int pthread_spin_destroy(pthread_spinlock_t *lock);
​
// 自旋锁操作
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
信号量机制(Semaphore)

信号量 是一个大于等于 0 的量,为 0 时候线程阻塞。通过 sem_pos 函数(非阻塞) 给信号量增加 1,sem_wait 函数(阻塞) 当 信号量大于 0 时候对其 减少 1,等于 0 时阻塞。

可以用来做通知用,也可以用来计数:

  • 信号量 可以控制多个 本来先后执行是随机无序的线程 的 执行顺序可控 和 可预测。例如 线程 A 在等待某件事,线程 B 完成了这件事后就可以给线程 A 发信号。

  • 当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。信号量 保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个 信号量 所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。

头文件:#include <semaphore.h>

常用 API:

int sem_init(sem_t *sem, int pshared, unsigned int value);    // 初始化一个信号量 
/* 该函数可以初始化一个信号量,第一个参数传入sem_t类型指针。
    第二个参数传入 0 代表线程控制(多线程内使用),否则为进程控制(多进程使用)。
    第三个参数表示信号量的初始值,0代表阻塞,1以及更大的数字代表初始值(不阻塞)。
    待初始化结束信号量后,若执行成功会返回0。 */
int sem_destroy(sem_t * sem);                                 // 销毁信号量
​
int sem_wait(sem_t * sem);                                    // 信号量减少 1(阻塞),P 操作
int sem_post(sem_t * sem);                                    // 信号量增加 1(非阻塞),V 操作
int sem_trywait(sem_t *sem);                                  // 信号量减少 1(非阻塞),成功返回 0 
/* sem_wait 函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行 sem - 1 的操作。
   sem_post 函数会释放指定信号量的资源,执行 sem + 1 操作。
   即信号量的 申请 与 释放 */
​
int sem_timedwait (sem_t * sem,const struct timespec * abstime); // 在 sem_wait 的 等待/阻塞 期间内最多 等待 abstime 秒的时间 就返回
int sem_post_multiple (sem_t * sem,int count);                // 一次 信号量增加 count(非阻塞)
​
int sem_getvalue(sem_t * sem, int * sval);                    // 获取当前信号量的值

例程:参考 pthread库-线程编程例程-来自百问网\01_文档配套源码\Pthread_Text12.cpthread库-线程编程例程-来自百问网\02_视频配套源码\pthread3.c 和 pthread4.c

条件变量机制

条件变量时一种同步机制,用来通知其他线程条件满足了,即 一个线程被挂起,直到某件事件发生。一般是用来通知对方共享数据的状态信息,因此条件变量时结合互斥量来使用的。

条件变量 是在多线程程序中用来实现 “等待--->唤醒” 逻辑常用的方法。

头文件:#include <pthread.h>

常用 API:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);  // 初始化条件变量
/* 若 不需要设置属性则 cond_attr 填为N ULL */
/* 另一种以默认方式初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; */
int pthread_cond_destroy(pthread_cond_t *cond);                               // 销毁条件变量
​
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);          // 无条件等待条件变量变为真
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *tsptr);  // 在给定时间内,等待条件变量变为真
​
int pthread_cond_signal(pthread_cond_t *cond); // 通知条件变量 条件满足啦,pthread_cond_signal 函数只会唤醒一个等待 cond 条件变量的线程
​
使用:
    
    发信号:
    pthread_mutex_lock(&mutex);    先加锁
        这里修改临界资源             再修改资源
    pthread_cond_signal(&cond);    再发条件变量信号 
    pthread_mutex_unlock(&mutex);  再解锁
​
    等待信号:
    pthread_mutex_lock(&mutex);    先加锁 /* 可以加锁的时候 加锁 然后往后运行,否则阻塞 */
    pthread_cond_wait(&cond, &mutex); 再等待条件变量信号 /* 如果条件不满足则,会 先解锁 mutex,然后 阻塞/休眠 在这里等待条件满足;条件满足后被唤醒,先加锁 mutex 然后取消阻塞 往后执行*/
        这里修改临界资源             再修改资源
    pthread_mutex_unlock(&mutex);  再解锁
    ...

例程:参考 pthread库-线程编程例程-来自百问网\02_视频配套源码\pthread5.c

信号(Signal)

在 一个进程的 多个线程 之间处理 信号(signal),与 进程中的信号处理不一样,需要多学、多用一下。

例如,当你发送一个SIGSTP信号给进程,这个进程的所有线程都会停止。因为所有线程内用同样的内存空间,所以对一个signal的handler都是一样的,但不同的线程有不同的管理结构所以不同的线程可以有不同的mask(引自 Linux线程的实现 & LinuxThread vs. NPTL & 用户级内核级线程 & 线程与信号处理 - blcblc - 博客园 (cnblogs.com))。

参考:

  • linux 多线程信号处理总结 - CobbLiu - 博客园 (cnblogs.com)。

  • pthread信号_track sun的博客-CSDN博客。

  • 经验性总结 linux多线程信号总结-businiaowyf-ChinaUnix博客。文章来源地址https://www.toymoban.com/news/detail-688664.html

到了这里,关于一文搞定Linux线程间通讯 / 线程同步方式-互斥锁、读写锁、自旋锁、信号量、条件变量、信号等等的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux——线程的同步与互斥

    目录 模拟抢火车票的过程 代码示例 thread.cc Thread.hpp 运行结果 分析原因 tickets减到-2的本质  解决抢票出错的方案 临界资源的概念 原子性的概念 加锁 定义 初始化 销毁 代码形式如下 代码示例1: 代码示例2: 总结 如何看待锁 申请失败将会阻塞  pthread_mutex_tyrlock 互斥锁实现

    2024年02月06日
    浏览(43)
  • Linux——多线程,互斥与同步

    目录 一.linux互斥 1.进程线程间的互斥相关背景概念 2.互斥量mutex 3.加锁互斥锁mutex 4.锁的底层原理  二.可重入VS线程安全 1.概念 2.常见的线程不安全的情况 3.常见的线程安全的情况  4.常见不可重入的情况  5..常见可重入的情况 6.可重入与线程安全联系  三.死锁 1.死锁四个必

    2024年02月05日
    浏览(34)
  • 【Linux】多线程互斥与同步

    互斥 指的是一种机制,用于确保在同一时刻只有一个进程或线程能够访问共享资源或执行临界区代码。 互斥的目的是 防止多个并发执行的进程或线程访问共享资源时产生竞争条件,从而保证数据的一致性和正确性 ,下面我们来使用多线程来模拟实现一个抢票的场景,看看所

    2024年02月09日
    浏览(40)
  • Linux-线程的同步与互斥

    🚀 临界资源:多线程指行流共享的资源叫做临界资源。 🚀 临界区:每个线程内部访问临界资源的代码片段叫做临界区。 🚀 互斥:任何时刻,互斥保证只有一个指行流进入临界区,访问临界资源,通常是对临界区起保护作用。 🚀 原子性:不被任何调度所打断的操作,该

    2024年02月09日
    浏览(48)
  • 【Linux】多线程2——线程互斥与同步/多线程应用

    💭上文主要介绍了多线程之间的独立资源,本文将详细介绍多线程之间的 共享资源 存在的问题和解决方法。 intro 多线程共享进程地址空间,包括创建的全局变量、堆、动态库等。下面是基于全局变量实现的一个多线程抢票的demo。 发现错误:线程抢到负数编号的票,为什么

    2024年02月10日
    浏览(43)
  • 【关于Linux中----线程互斥与同步】

    先来用代码模拟一个抢票的场景,四个线程不停地抢票,一共有1000张票,抢完为止,代码如下: 执行结果如下: 可以看到,最后出现了票数为负数的情况,很显然这是错误的,是不应该出现的。 为什么会出现这种情况? 首先要明确,上述的几个线程是不能同时执行抢票的

    2023年04月08日
    浏览(43)
  • 【Linux】多线程 --- 线程同步与互斥+生产消费模型

    人生总是那么痛苦吗?还是只有小时候是这样? —总是如此 1. 假设现在有一份共享资源tickets,如果我们想让多个线程都对这个资源进行操作,也就是tickets- -的操作,但下面两份代码分别出现了不同的结果,上面代码并没有出现问题,而下面代码却出现了票为负数的情况,这

    2024年02月06日
    浏览(45)
  • 『Linux』第九讲:Linux多线程详解(三)_ 线程互斥 | 线程同步

    「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(二),今天这篇是 Linux多线程详解(三),内容大致是线程互斥与线程同步,讲解下面开始! 「归属专栏」Linux系统编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「每篇一句

    2024年02月02日
    浏览(74)
  • Linux pthread线程操作 和 线程同步与互斥操作

    在Linux系统中玩线程,使用pthread,这篇博客记录如何 创建线程 和 使用线程 和线程的 同步 与 互斥 。 还有一份nginx线程池的代码供大家阅读学习! 目录 一、简介 什么是线程 线程的优点、缺点 线程的应用场合 二、线程的使用 1.  创建线程 - pthread_create 2.  线程的终止 - pt

    2024年02月02日
    浏览(31)
  • 【Linux】多线程02 --- 线程的同步互斥问题及生产消费模型

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 线程同步互斥问题是指多线程程序中,如何保证共享资源的正确访问和线程间的协作。 因为线程互斥是实现线程同步的基础和前提,我们先讲解线程互斥问题。 在多线程中,假设我们有一个黄牛抢票的代码,其中有一份共享资源

    2024年02月08日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包