【Window】线程同步概述

12 篇文章 4 订阅

第一节:【Window】创建线程的3种方式
第二节:【Window】线程同步概述
第三节:【Window】线程同步方式1——临界区(关键代码段)
第四节:【Window】线程同步方式2——互斥量
第五节:【Window】线程同步方式3——事件
第六节:【Window】线程同步方式4——信号量

在这里插入图片描述

1 为什么要进行线程同步?

在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。

如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。

例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。

这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步

2 什么是线程同步和互斥

线程之间通信的两个基本问题是互斥同步

■■ 同步就是协同步调按预定的先后次序进行运行。

如:你说完,我再说。
这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程同步是多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步),也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的。

■■ 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性
当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。


线程互斥是一种特殊的线程同步。实际上,互斥和同步对应着线程间通信发生的两种情况:

■■ 当有多个线程访问共享资源而不使资源被破坏时;
■■ 当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。


从大的方面讲,线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。

  • 用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。
  • 内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式

3 线程同步的方式和机制

在WIN32中,同步机制主要有以下几种:

  • 临界区(Critical section);
  • 互斥量(mutex);
  • 事件(Event);
  • 信号量(semaphore).

◉◉ 信号量(Semaphore)、事件对象(Event):事件对象是以通知的方式进行控制,主要用于同步控制

◉◉ 临界区(Critical Section)、互斥对象(Mutex):主要用于互斥控制;都具有拥有权的控制方法,只有拥有该对象的线程才能执行任务,所以拥有,执行完任务后一定要释放该对象。

3.1) 临界区

通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问

在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。它并不是核心对象,不是属于操作系统维护的,而是属于进程维护的。

总结下关键段:

  • 关键段有初始化、销毁、进入和离开关键区域四个函数。
  • 关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题
  • 推荐关键段与旋转锁配合使用。

详细说明:【Window】线程同步方式1——临界区(关键代码段)

3.2) 互斥对象

互斥对象和临界区很像,采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限

因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。

互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。

总结下互斥量Mutex:

  • 互斥量是内核对象,它与关键段都有“线程所有权”,所以不能用于线程的同步
  • 互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某进程意外终止所造成的“遗弃”问题。

详细说明:【Window】线程同步方式2——互斥量

3.3) 事件对象

通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作

总结事件Event:

  • 事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
  • 事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
  • 事件可以解决线程间同步问题,因此也能解决互斥问题

详细说明:【Window】线程同步方式3——事件

3.4) 信号量

信号量也是内核对象。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目

在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。

  • 一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。
  • 当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。
  • 线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore ()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数

详细说明:【Window】线程同步方式4——信号量

3.5)区别

互斥量和信号量的区别:

  1. 互斥量用于线程的互斥,信号量用于线程的同步
    互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

  2. 互斥量值只能为0/1,信号量值可以为非负整数
    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

  3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法确定超时值。

4 并发和并行、异步和同步

4.1 并发和并行

4.1.1)并发

在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥

4.1.1.1)互斥

进程间相互排斥的使用临界资源的现象,就叫互斥。

4.1.1.2)同步

进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发

4.1.2)并行

单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生

4.2 多线程

多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行

4.3 异步和同步

异步和同步是相对的:

  • 同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行
  • 异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作
  • 线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
  • 异步和多线程并不是一个同等关系,异步是最终目的。多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理

为了对以上概念的更好理解举一个简单例子:
假设我要做3件事情:✨烧开水,✨举杠铃100下, ✨洗衣服

烧开水 —— 准备烧开水:1分钟;等开水烧开:8分钟 ;关掉烧水机:1分钟。
举杠铃100下—— 举杠铃100下:10分钟。
洗衣服——准备洗衣服:1分钟; 等衣服洗好:5分钟 ;关掉洗衣机:1分钟。

单核情况下:

  • 同步:需要做的时间为:1+ 8 +1 + 10 + 1+ 5 +1 = 27 分钟
  • 异步:就是在等的时候,可以切换去做别的事情:
    准备烧开水(1) + 准备洗衣服(1) + 举50下杠铃 (5)+关洗衣机(1) + 举杠铃10下(1) + 关烧水机(1) + 举40下杠铃(4)分钟——1+1+5+1+1+1+4=14 分钟

双核 异步 并行:

  • 核1: 准备烧开水(1) + 举杠铃50下(5) + 等待(3) + 关掉烧水机 (1)
  • 核2: 准备洗衣服(1) + 举杠铃50下(5) + 关掉洗衣机(1) + 等待 (3)

其实只花了 1+5+3+1 = 10分钟,其中还有双核都等待了3分钟。

双核 异步 非并行

  • 核1: 举杠铃100下(10)分钟
  • 核2: 准备烧开水 (1) + 准备洗衣服 (1) + 等待(5) + 关掉烧水机 (1) + 等待 (1) + 关掉洗衣机 (1)

其实只花了 1+5+3+1 = 10分钟。

多线程的做法
单核下

  • 线程1 :准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟
  • 线程2 :举杠铃100下 10分钟
  • 线程3 ;准备洗衣服 1分钟, 等衣服洗好5 分钟 , 关掉洗衣机 1分钟

cpu 最理想的切换方式

时间第1分钟第2分钟第3~7分钟第8分钟第9分钟第10分钟第11-14分钟
线程1准备烧开水 (1)sleep(1)sleep(5)sleep(1)sleep(1)关掉烧水机(1)exit
线程2sleep(1)sleep(1)举杠铃50 (5)sleep(1)举杠铃10(1)sleep(1)举杠铃40下(4)
线程3sleep(1)准备洗衣服(1)sleep(5)关洗衣机(1)exit

最后使用了 14分钟和异步是一样的。
但是实际上是不一样的,因为线程不会按照我们设想的去跑, 如果线程2举杠铃先跑,整个流程的速度就下来了。

异步和同步的区别, 在io等待的时候,同步不会切走,浪费了时间。

如果都是独占cpu的业务, 比如举杠铃的业务, 在单核情况下 多线和单线没有区别。

多线程的好处,比较容易的实现了异步切换的思想,因为异步的程序很难写的。多线程本身程还是以同步完成,但是应该说比效率是比不上异步的。而且多线很容易写,相对效率也高。

多核的好处,就是可以同时做事情, 这个和单核完全不一样的。

学习:
线程同步(互斥锁与信号量的作用与区别)
C++多线程——线程同步
华丽的双鱼
c++之多线程中“锁”的基本用法
C++多线程:互斥锁
C++中多线程的四种控制方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值