线程间一般无需特别的手段进行通信,由于线程间能够共享数据结构,也就是一个全局变量能够被两个线程同时使用。只是要注意的是线程间须要做好同步!

使用多线程的理由:

1. 一个是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而执行于一个进程中的多个线程,它们彼此之间使用同样的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,并且,线程间彼此切换所需的时间也远远小于进程间切换所须要的时间。

2. 使用多线程的理由之二是, 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递仅仅能通过通信的方式进行,这样的方式不仅费时,并且非常不方便。线程则不然,因为同一进程下的线程之间共享数据空间,所以一个线程的数据能够直接为其他线程所用,这不仅快捷,并且方便。比起进程复杂的通信机制(管道、匿名管道、消息队列、信号量、共享内存、内存映射以及socket等),线程间通信要简单的多.

同一进程的不同线程共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段,所以线程之间可以方便、快速地共享信息。只需要将数据复制到共享(全局或堆)变量中即可。

多线程的进程地址空间:

当然,数据的共享也带来其他一些问题,有的变量不能同一时候被两个线程所改动,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最须要注意的地方,需要考虑线程安全。

写线程安全的代码依靠线程同步.

线程同步:

如果变量时只读的,多个线程同时读取该变量不会有一致性问题,但是,当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步。

线程同步的方式

1. 互斥量

互斥量本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量。对互斥量进行枷锁以后,其他视图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程就会看到互斥量依然是锁着,只能再次阻塞等待它重新变成可用,这样,一次只有一个线程可以向前执行。

互斥量的死锁:

如果一个线程试图对同一个锁进行两次枷锁,那么它自身就会陷入死锁。

程序中使用一个以上的互斥量时,如果一个线程一直占有第一个互斥量,并且在试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,这时,两个线程都在相互请求另一个线程占用的资源,所以这两个线程都无法向前运行,就产生了死锁。

死锁的处理策略:---需要进一步学习!

1、预防死锁:破坏死锁产生的四个条件:互斥条件、不剥夺条件、请求和保持条件以及循环等待条件。

2、避免死锁:在每次进行资源分配前,应该计算此次分配资源的安全性,如果此次资源分配不会导致系统进入不安全状态,那么将资源分配给进程,否则等待。算法:银行家算法。

3、检测死锁:检测到死锁后通过资源剥夺、撤销进程、进程回退等方法解除死锁。

2. 读写锁

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

当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。  另外,虽然各个操作系统对读写锁的实现方式不同,但当读写锁处于读模式锁住的状态,而这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期被占用导致等待的写模式锁请求一直得不到满足。

读写锁非常适合对数据结构读的次数远大于写的情况。

3. 条件变量

条件变量是线程可用的另一种同步机制。互斥量用于上锁,条件变量则用于等待,并且条件变量总是需要与互斥量一起使用

等待线程

使用pthread_cond_wait前要先加锁;

pthread_cond_wait内部会先解锁(该函数的第一个入参是互斥锁),然后等待条件变量被其它线程激活,如果没有被唤醒, 该线程将一直休眠, 也就是说, 该线程将一直阻塞在这个pthread_cond_wait调用中;

pthread_cond_wait被激活后会再自动加锁;pthread_cond_wait目前的逻辑是

  1. 解锁mutex
  2. just_wait(不含mutex操作的wait,单纯等待signal),阻塞,直到某个signal才返回。
  3. 加锁mutex

第1和第2合起来是原子操作。

“用户写程序的时候通常是发现当前一些变量的值比如说变量a并不满足期待的条件,所以选择调用cond_wait把当前线程挂起,期待别的线程修改a的值。变量a自然是多个线程之间共享的,所以本意是让你用这个mutex保护a的,而不是说pthread_cond_wait的内部实现需要用这个mutex保护什么”。

// 条件测试 :

pthread_mutex_lock(mtx);

while(pass == 0)  // pthread_cond_wait()函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值

pthread_cond_wait(mtx,qReady);//qReady为条件变量

pthread_mutex_unlock(mtx);

// 条件发生修改,对应的signal代码

pthread_mutex_lock(mtx);

pass = 1;

pthread_mutex_unlock(mtx);

pthread_cond_signal(qReady);//发送信号,激活等待的线程

唤醒

唤醒条件有两种形式,pthread_cond_signal()唤醒一个等待该条件的线程,存在多个等待线程时按入队顺序唤醒其中一个;而pthread_cond_broadcast()则唤醒所有等待线程。

唤醒丢失问题:

唤醒丢失往往会在下面的情况下发生:

一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间, 这时没有线程正在处在阻塞等待的状态下。

知乎:

posix 条件变量提醒是并不像go channel一样有缓冲或者可能生产者阻塞的,生产者通过条件变量发起提醒时如果没有任何消费者在等待条件变量,这个提醒就被无声的被丢弃了,如他名字所述pthread_cond_signal只是个异步信号而已。

注意:

pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的线程退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

条件变量应该还有更加丰富的应用,例如知乎中提到的生产者消费者模型的实现。

4. 信号量

如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。 

线程的信号和进程的信号量类似,使用线程的信号量可以高效地完成基于线程的资源计数。信号量实际上是一个非负的整数计数器,用来实现对公共资源的控制。在公共资源增加的时候,信号量就增加;公共资源减少的时候,信号量就减少;只有当信号量的值大于0的时候,才能访问信号量所代表的公共资源。

常用头文件:

#include <semaphore.h>

常用函数:

sem_t sem_event;

int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化一个信号量

int sem_destroy(sem_t * sem);//销毁信号量

int sem_post(sem_t * sem);//信号量增加1

int sem_wait(sem_t * sem);//信号量减少1

int sem_getvalue(sem_t * sem, int * sval);//获取当前信号量的值

---需要深究。

5. 自旋锁

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

6. 屏障

屏障是指用户可以协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从改点继续执行。

还记pthread_join函数吗?在子线程退出之前,主线程要一直等待。pthread_join函数就是一种屏障,它允许一个线程等待,直到另一个线程退出。屏障允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作。

如果我们要让主线程在所有工作线程完成之后再做某项任务,一般把屏障计数值设为工作线程数加1,主线程也作为其中一个候选线程。

Ref:

《Unix环境高级编程-第三版》

https://blog.csdn.net/a987073381/article/details/52029070

https://blog.csdn.net/hmxz2nn/article/details/80786188

https://www.cnblogs.com/wsw-seu/p/8036218.html

https://www.zhihu.com/question/24116967

linux线程间同步方式总结梳理的更多相关文章

  1. linux线程间同步方式汇总

    抽空做了下linux所有线程间同步方式的汇总(原生的),包含以下几个: 1, mutex 2, condition variable 3, reader-writer lock 4, spin loc ...

  2. Linux线程间同步的几种方式

    信号量 信号量强调的是线程(或进程)间的同步:"信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞 ...

  3. linux线程间同步(1)读写锁

    读写锁比mutex有更高的适用性,能够多个线程同一时候占用读模式的读写锁.可是仅仅能一个线程占用写模式的读写锁. 1. 当读写锁是写加锁状态时,在这个锁被解锁之前,全部试图对这个锁加锁的线程都会被堵塞 ...

  4. Linux系统编程(29)——线程间同步(续篇)

    线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行.在pthread库中通过条件变 ...

  5. rtt学习之线程间同步与通信

    一 线程间的同步与互斥:信号量.互斥量.实践集 线程互斥是指对于临界区资源访问的排它性,如多个线程对共享内存资源的访问,生产消费型对产品的操作.临界区操作操作方法有: rt_hw_interrupt_ ...

  6. c++11 线程间同步---利用std::condition_variable实现

    1.前言 很多时候,我们在写程序的时候,多多少少会遇到下面种需求 一个产品的大致部分流程,由工厂生产,然后放入仓库,最后由销售员提单卖出去这样. 在实际中,仓库的容量的有限的,也就是说,工厂不能一直生 ...

  7. C#线程间同步无法关闭

    用C#做了个线程间同步的小程序,但每次关闭窗口后进程仍然在,是什么原因? 解决方法: 要加一句 线程.IsBackground = true; 否则退出的只是窗体 上面的方法没看懂... MSDN上说 ...

  8. conditon_variable(条件变量)用于线程间同步

    conditon_variable(条件变量)用于线程间同步 condition_variable有5个函数,函数名及对应的功能如下: wait阻塞自己,等待唤醒 wait_for阻塞自己,等待唤醒, ...

  9. Linux 进程间通讯方式 pipe()函数 (转载)

    转自:http://blog.csdn.net/ta893115871/article/details/7478779 Linux 进程间通讯方式有以下几种: 1->管道(pipe)和有名管道( ...

随机推荐

  1. Postgresql operator does not exist: numeric = character varying

    )),series_name from svcm_t_series_res: 原来series_id是numeric型,现在转换为varchar(64):

  2. 过滤器的API

    1.API a.生命周期(和servletcontext相似): (1)创建:服务器启动的时候创建(执行init方法). (2)销毁:服务器关闭的时候销毁(执行destory方法). b.filter ...

  3. Eclipse Maven问题小记

    Eclipse Maven Web工程报错:java.lang.ClassNotFoundException: ContextLoaderListener 原因:打包项目时没有把相关Maven依赖包打 ...

  4. 【POJ1068】Parencodings

    题目传送门 本题知识点:模拟 这是一道恐怖的括号题.题意稍微理解以下还是可以的. 我们针对样例来理解一下 S.P.W 到底是什么意思: S:( ( ( ( ) ( ) ( ) ) ) ) P: \(P ...

  5. 牛顿插值法(c++)

    X Y 0.40 0.41075 0.55 0.57815 0.65 0.69675 0.80 0.88811 0.90 1.02652 1.05 1.25382 #include using nam ...

  6. #C++初学记录(动态规划(dynamic programming)例题1 钞票)

    浅入动态规划 dynamic programming is a method for solving a complex problem by breaking it down into a coll ...

  7. 【转】使用eclipse的todo标签管理任务

    Eclipse中的一些特殊的注释技术包括:    1.    // TODO —— 表示尚未完成的待办事项.    2.    // XXX —— 表示被注释的代码虽然实现了功能,但是实现方案有待商榷 ...

  8. GPU性能越来越强大,为何直到现在还是不能取代CPU?

      从思路上说,GPU相当于火车,一个车头带几十节车厢,一下子把成千上万吨货全给你拉目的地:CPU相当于汽车,拉货旅游样样能干.因此,如果单纯比运力,一列火车比得过成百上千辆汽车:但如果几百人有几百个 ...

  9. jQuery跳出each循环:JS报错:illegal break statement

    今天在JS中运用jquery中each写一个简单的循环语句时,在执行跳出循环操作时,遇到JS报错:Uncaught SyntaxError: illegal break statement 非法的br ...

  10. uploadifive 1.1.2 动态传参

    之前用过Flash版本的uploadify,写过一篇关于uploadify动态传参的文章(点击打开链接).后来有了HTML5版本的上传控件,叫uploadifive,测试着用了一下,效果还不错.这里主 ...