在介绍线程同步/互斥之前,我们先要理解同步与互斥的概念,引用书上的解释来说明这2个概念:
1、线程(进程)同步的主要任务
在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。
2、线程(进程)之间的制约关系?
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
(1)间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。
(2)直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。
归纳如下:
1、互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
2、同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
3、同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。
4、互斥是一种特殊的同步。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。

下面来看看线程同步的5种常用方式:
1、 临界资源(CCriticalSection)/关键段
当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。(临界区只能同一进程中线程使用,不能跨进程使用)
临界资源:同时只允许一个进程使用的资源。
临界区:进程中用于访问临界资源的代码段,又称临界段。
每个进程的临界区代码可以不同,临界区代码由于要访问临界资源,因此要在进入临界区之前进行检查,至于每个进程对临界资源进行怎样的操作,这和临界资源及互斥同步管理是无关的。
使用方式:
1.定义临界区对象
CcriticalSection g_CriticalSection;
2.在访问共享资源(代码或变量)之前,先获得临界区对象
g_CriticalSection.Lock();
3.访问共享资源后,则放弃临界区对象
g_CriticalSection.Unlock();
临界区一般使用锁的方式来实现,常见的互斥锁和读写锁:提供对临界资源的保护,当多线程试图访问临界资源时,都必须通过获取锁的方式来访问临界资源。(临界资源:是被多线程共享的资源)当读写线程获取锁的频率差别不大时,一般采用互斥锁,如果读线程访问临界资源的频率大于写线程,这个时候采用读写锁较为合适,读写锁允许多个读线程同时访问临界资源,读写线程必须互斥访问临界资源。读写锁的实现采用了互斥锁,所以在读写次数差不多的情况下采用读写锁性能没有直接采用互斥锁来的高。
最后总结下关键段:
1.关键段共初始化化、销毁、进入和离开关键区域四个函数。
2.关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
3.推荐关键段与旋转锁/自旋锁配合使用。关于锁,见本文后面

(注意:下面说的3种同步手段,事件,互斥量,信号量都是内核对象,都可以跨进程使用)

2、 互斥量/互斥锁(CMutex)
互斥量多用于多进程之间的线程互斥,用来确保一个线程独占一个资源的访问。而且能正确处理资源遗弃的问题(“遗弃”问题就是——占有某种资源的进程意外终止后,其它等待该资源的进程能否感知。,而事件与信号量都无法处理遗弃问题,更多关于遗弃问题的分析,参考此处)
互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。
相关函数:

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_destroy(pthread_mutex *mutex);
int pthread_mutex_unlock(pthread_mutex *

3、 事件(CEvent)/条件变量
事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。或者按照条件变量的说法,提供线程之间的一种通知机制。
每个Cevent对象可以有两种状态:有信号状态和无信号状态。
Cevent类对象有两种类型:人工事件和自动事件。
相关函数:

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);  //解除所有线程的阻塞

4、 信号量(CSemphore)
当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。
信号量提供对临界资源的安全分配。如果存在多份临界资源,在多个线程争抢临界资源的情况下,向线程提供安全分配临界资源的方法。如果临界资源的数量为1,将退化为锁。
IPC方式中也有信号量,常常配合ipc共享内存来使用,作为进程之间以及同一进程不同线程间的同步手段。
使用方式:
CSemaphore类对象保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。
线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。
相关函数:
信号量函数的名字都以”sem_”打头。线程使用的基本信号量函数有四个。

#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);

这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

两个原子操作函数:

int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);

这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。
int sem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。
5、令牌:
一种高级的线程同步的方法。它既提供锁的安全访问临界资源的功能,又利用了条件变量使得线程争夺临界资源时是有序的。

注意:
1、临界区和互斥量都有“线程所有权”的概念,所以它们是不能用来实现线程间的同步的,只能用来实现互斥。
2、事件和信号量都可以实现线程和进程间的互斥和同步。但是事件和信号量都无法解决遗弃问题。
3、临界区的效率是最高的,因为它不是内核对象。但是临界区不能跨进程使用。
事件,互斥量,信号量都是内核对象,可以跨进程使用,但相应的效率也会低很多。

关于锁的种类与特点,参考:linux锁的种类与特点
关于多进程、多线程,参考:多进程、多线程以及如何选择?

线程同步常用方式与区别相关推荐

  1. 关于C语言中线程同步的方式

    C语言中线程同步的方式 线程同步 互斥锁 读写锁 条件变量 信号量 线程同步 在多线程环境中,线程之间由于竞争共享资源(临界资源)容易引起数据不一致的问题.一般采用互斥锁(互斥信号量)解决,保证只有一 ...

  2. 进程/线程同步的方式和机制,进程间通信

    一.进程/线程间同步机制. 临界区.互斥区.事件.信号量四种方式 临界区(Critical Section).互斥量(Mutex).信号量(Semaphore).事件(Event)的区别 1.临界区: ...

  3. Java实现线程同步的方式

    1. synchronized关键字 synchronized关键字保证在同一时刻,只有一个线程可以执行某个对象内某一个方法或某一段代码块. 重量级锁.包含两个特征:互斥性和可见性. synchron ...

  4. Qt创建线程两种方式的区别

    使用QT创建线程有两种方式,方式A使用moveToThread,方式B是直接继承QThread.差异主要在于方式A的槽函数将会在新线程中运行,而方式B的槽函数在旧线程中运行. 结论如下: PS:旧线程 ...

  5. Android面试之线程同步的方法

    一 什么是进程和线程?进程和线程的区别? 进程是资源分配的最小单元,线程是程序执行的最小单元(进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位) 进程有自己的独立地址空间,每启动一个 ...

  6. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  7. C#并行编程(6):线程同步面面观

    理解线程同步 线程的数据访问 在并行(多线程)环境中,不可避免地会存在多个线程同时访问某个数据的情况.多个线程对共享数据的访问有下面3种情形: 多个线程同时读取数据: 单个线程更新数据,此时其他线程读 ...

  8. Window下线程与线程同步总结

    目录 一 线程创建与使用 线程创建函数 CreateThread与_beginthreadex 等待函数 WaitForSingleObject 和 WaitForMultipleObjects 例程 ...

  9. Android-线程常用方法-线程同步

    线程常用方法: 1.start():线程调用该方法将启动线程从新建状态进入就绪,一旦轮到享用CPU资源时,就开始自己的生命周期 2.run():Thread类的run()方法与Runnable接口的r ...

最新文章

  1. 简单的正则表达式过滤网址
  2. java-集合排序,队列,散列表map以及如何遍历
  3. python显示无效语法怎么处理-python – 无效语法(对于循环括号/括号)
  4. 计算机组装各个配件的选用,组装电脑各个配件装机心得与经验
  5. MySQL规格列表(硬件优化上限)
  6. DeepMind:所谓SACX学习范式
  7. Android获取手机联系人或通讯录的基本信息(如姓名、电话)
  8. argv python 提示输入_Python解释器
  9. linux启动写入了mbr,一、Linux系统启动(MBR)
  10. 蒙特卡罗树搜索+深度学习 -- AlphaGo原版论文阅读笔记
  11. linux 基础知识考试试题,Linux常识型试题
  12. 最新亲测可用的免费google翻译api
  13. 通讯录系统课程设计——链表实现——c语言
  14. CATIA二次开发—遍历结构树
  15. list筛选数据 python_「每日一练」巧用python对列表进行筛选
  16. 《拥抱机器人时代——Servo杂志中文精华合集》——4.3 理解智能设备
  17. 笔记本win10开启wifi共享wifi
  18. js简易版歌单播放,可切换下一首
  19. 51万年历林贤文:做一个不“安分”的程序员
  20. 普华i-VirtualApp应用交付系统介绍

热门文章

  1. 洛谷 3393 逃离僵尸岛
  2. PDM入门指南:一文带你了解PDM的基本知识
  3. 如何评价 Qt 的发展前景?
  4. mysql safe模式解除
  5. 三维模型3ds模型下载网站
  6. ESP32 开发笔记(五)XPT2046 触摸
  7. 网络安全实战攻防演练应急处置预案
  8. React使用pubsub-js订阅发布和取消订阅
  9. windows建立和删除软连接
  10. 微信公众号二维码查看攻略