线程同步的几种方法_异步和同步的区别在哪

线程同步的几种方法_异步和同步的区别在哪线程同步的一些方式作者:勉辉著作权归作者所有。商业转载请联系作者进行授权,非商业转载请注明出处。一. 为什么需要线程同步随着多核技术的快速发展,多线程编程也越来越重要。但往往在一些项目中,我们会遇到一些容器或变量在多线程的读写下崩溃或结果不正常的问题。所以我们需要同步机制,来保护这些变量。首先我们

线程同步的一些方式   作者:勉辉   著作权归作者所有。商业转载请联系作者进行授权,非商业转载请注明出处。   一. 为什么需要线程同步   随着多核技术的快速发展,多线程编程也越来越重要。但往往在一些项目中,我们会遇到一些容器或变量在多线程的读写下崩溃或结果不正常的问题。所以我们需要同步机制,来保护这些变量。   首先我们可以观察下面一段代码   一般来说变量的值是两个线程中各次的自增,结果为,可是实际的输出结果大概率会小于   这时我们观察其汇编代码:
线程同步的几种方法_异步和同步的区别在哪
线程同步的几种方法_异步和同步的区别在哪   其中线程中执行的lambda函数对应汇编代码段为   线程中的循环对应的汇编代码段为   线程中的++count指令对应的汇编代码段即为   仔细观察代码段,我们可以看到在C/C++中简单的自增指令翻译为汇编语言变为了多条汇编指令   其中重要的一部分为   中保存了变量的地址,然后对寄存器进行了一个取,加1,和写回的操作。那么两个线程中同样的代码段,由于操作系统的线程调度,是有可能指令穿插执行的。   可以想象以下情况:   线程1在执行代码段的时候,还未进行写回的操作,线程2在取了地址中的值,那么此时地址中的值还未被线程1写回更新,所以两个线程在这种情况下将对写入一个相同的值。   所以我们需要同步的概念,即保证代码段正在执行时不许有在同时执行,保证一段程序在并发(多进程或线程)的情况下中如何去顺序唯一的被执行的,即操作系统提供锁的概念。   那么可以简易的得出模型   二. 互斥量   操作系统提供了临界区和临界资源的概念。   临界区不是内核对象,而是系统提供的一种数据结构,程序中可以声明一个该类型的变量,之后用它来实现对资源的互斥访问。posix api中提供了提供了互斥量的概念,互斥量类似于锁,它允许程序员锁住某个资源,从而让每次只有一个线程去访问它。我们可以在进入临界区时对互斥量上锁,然后在完成操作后解锁。简单来说,可以将临界区看作是一个代码段。   对于上述例子来说,我们只需要将这条指令放入临界区即可。   三. 自旋锁   由于多线程的核心是CPU的时间分片,所以同一时刻只能有一个线程到锁。那么就面临一个问题,那么没有到锁的线程应该怎么办?通常有两种处理方式:一种处理方式就是把自己挂起,等待重新调度请求,这种叫做互斥锁;一种是没有到锁的线程就一直循环等待判断该资源是否已经释放锁,这种锁叫做自旋锁,它不用将线程阻塞起来(NON-BLOCKING)。   因为自旋锁避免了操作系统进程调度和线程切换,所以自旋锁通常适用在时间比较短的情况下。由于这个原因,操作系统的内核经常使用自旋锁。但是,如果长时间上锁的话,自旋锁会非常耗费性能,它阻止了其他线程的运行和调度。线程持有锁的时间越长,则持有该锁的线程将被 调度程序中断的风险越大。如果发生中断情况,那么其他线程将保持旋转状态(反复尝试锁),而持有该锁的线程并不打算释放锁,这样导致的是结果是无限期推迟,直到持有锁的线程可以完成并释放它为止。   自旋锁适用于短期内进行轻量级的锁定。   四. 原子操作   原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。如果CPU不是简单地读/写一个变量,而是需要修改一个变量的值,那么它首先需要将变量的值从内存读取到寄存器中(read),然后修改寄存器中变量的值(modify),最后将修改后的值写回到该变量所在的内存位置(write),这一过程被称为RMW(Read-Modifiy-Write)。RMW操作有可能被中断所打断,因而不是原子的。那如何保证RMW操作的原子性呢?最简单的方法就是关闭中断。   实现方式:   4.1 关中断(单核系统)   如果是单核系统,以上述操作为例,只需要在进行RMW操作的时候,关闭中断,阻止线程切换,那么就可以实现原子性。   4.2 总线加锁   多个CPU通过共享的总线和内存相连接,如果它们同时申请访问内存,那么总线就会从硬件上进行仲裁,以确定接下来哪一个CPU可以使用总线,然后将总线授权给它,并且允许该CPU完成一次原子的load操作或者store操作。在完成每一次操作之后,就会重复这个周期:对总线进行仲裁,并授权给另一个CPU。每次只能有一个CPU使用总线。   x86处理器常用的做法是给总线上锁(bus lock),以获得在一定的时间窗口内对总线独占的授权,就好像是一个CPU在告诉总线说“在我完成之前,别让其他CPU来读写内存的数据”。   有一些指令,比如XCHG,在执行时会被硬件自动/隐式地加上LOCK#信号,实现总线的锁定。软件也可以显示地在指令前面加上一个名为”lock”的前缀来达到相同的效果,锁总线的时间等于指令执行的时间。不过,并非所有的指令都可以加”lock”前缀,允许添加的指令包括CMPXCHG, 用于算术运算的ADD, SUB, INC, DEC,以及用于位运算的BTS, BTC等。   4.3 锁缓存   为了实现多核Cache一致性,现在的硬件基本采用MESI协议(或者MESI变种)维护一致性。因此我们可以借助多核Cache一致性协议MESI实现原子操作。我们知道Cache line的状态处于Exclusive或者Modified时,可以说明该变量只有当前CPU私有Cache缓存了该数据。所以我们可以直接修改Cache line即可更新数据。并且MESI协议可以帮我们保证互斥。当然这不能不能保证RMW操作期间不被打断,因此我们还需要做些手脚实现原子操作。   我们依然假设只有2个CPU的系统。当CPU0试图执行原子递增操作时。a) CPU0发出”Read Invalidate”消息,其他CPU将原子变量所在的缓存无效,并从Cache返回数据。CPU0将Cache line置成Exclusive状态。然后将该cache line标记locked。b) 然后CPU0读取原子变量,修改,最后写入cache line。c) 将cache line置位unlocked。   在步骤a)和c)之间,如果其他CPU(例如CPU1)尝试执行一个原子递增操作,CPU1会发送一个”Read Invalidate”消息,CPU0收到消息后,检查对应的cache line的状态是locked,暂时不sigusoft消息(CPU1会一直等待CPU0sigusoftInvalidate Acknowledge消息)。直到cache line变成unlocked。这样就可以实现原子操作。我们称这种方式为锁cache line。这种实现方式必须要求操作的变量位于一个cache line。   在x86-64 gcc下我们可以定义以下函数,简单的观察其实现机制   观察自增汇编代码:   此时 指令被 ,根据锁总线或缓存一致性协议,保证了对共享变量的原子操作   五. CAS   (Compare-And-Swap)是一种多线程同步的机制,它能够保护共享数据结构的原子性,从而避免并发访问时的数据竞争问题。它通常适用于那些需要经常被修改、多个线程并发访问时可能会产生竞争问题的数据结构,例如计数器、队列等。   与传统的锁机制不同, 采用了乐观锁并发控制算法。它通过比较共享数据的当前值和要修改的新值,仅在当前值等于预期值时才实际执行修改操作。如果当前值与预期值不相等,则表示其他线程已经修改了该值,此时需要重新最新的值并重试。   伪代码:   算法在操作系统底层是对应一条字节码指令,指令的作用就是比较并且交换操作数,并且在多处理器的情况下,会在字节码指令前面加上前缀,确保了对内存的读写操作的原子性。   六. 使用CAS特性实现一个简易的无锁队列   enqueue操作会将一个新的素data加入到队列中。它需要将新的节点插入到队列的末尾,并更新tail指针指向新插入的节点。如果队列为空,它还需要将head指针指向新的节点。enqueue操作使用了std::atomic的exchange函数来原子地交换节点指针。   dequeue操作会从队列头部取出一个素。它需要从head指针指向的节点开始向队列末尾查找一个可以读取数据的节点。如果找到了一个节点可以读取数据,则需要原子地更新head指针指向下一个节点,并返回这个可以读取数据的节点中的数据。dequeue函数也需要用std::atomic中的compare_exchange_strong函数来原子地修改head指针指向下一个节点。   七. 总结   为了使程序能在多线程或多进程下运行得到正确的结果,我们必须使用一些同步方法进行指令的同步。以上简单的概述了下最基本的同步方法互斥锁,在保证锁后能快速释放的情况下,使用自旋锁减少一些不必要的线程切换,提升性能。在追求更轻量级的情况下,可以使用操作系统和编译器提供的原子指令避免同一个资源被多个线程代替一些锁操作,实现高性能的无锁编程。   参考文档:   Linux 内核同步(二):自旋锁(Spinlock)spin_lock爱洋葱的博客-CSDN博客   https://zhuanlan.zhihu.com/p/

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/33559.html

(0)
上一篇 2024年 9月 10日 下午6:21
下一篇 2024年 9月 10日 下午6:24

相关推荐

关注微信