精华内容
下载资源
问答
  • 线程同步机制

    千次阅读 2019-02-14 17:52:46
    从广义上说,Java平台提供的线程同步机制包括锁、volatile关键字、final关键字、static关键字和一些相关的API,如Object.wait( )/.notify( )等   1、锁的概述和概念: a 线程安全问题的产生: 多个线程并发访问...

     

     

    一、线程同步机制

    从广义上说,Java平台提供的线程同步机制包括锁、volatile关键字、final关键字、static关键字和一些相关的API,如Object.wait( )/.notify( )等

     

    1、锁的概述和概念:

    a 线程安全问题的产生:

    多个线程并发访问共享变量、共享资源;

    解决办法:

    一个共享变量或者资源只能被一个线程访问,访问结束后其他线程才能访问(用锁)。

     

    b、用锁保护状态:

    1、对于可能被多个线程同时访问的可变状态变量(比如银行总账户),在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是有这个锁保护的。

    2、每个共享的和可变的变量都应该只有一个锁来保护,从而使维护人员知道是哪一个锁。

    3、对于每个包含多个变量的不变性条件,期中涉及的所以变量都需要由同一个锁来保护。

     

    • 许多线程安全类使用的加锁模式是,将可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码进行同步
    • 每个对象都有一个内置锁,只是为了免去显式地创建锁对象

     

    c、锁的几个概念

    锁的争用:锁可以被看做多线程程序访问共享数据时所持有的一种排他性资源

    锁的调度:包括公平调度策略和非公平调度策略。内部锁属于非公平锁;显示锁则两者都支持。

    锁的粒度:一个锁保护的共享数据的大小称粒度。锁的粒度过粗会导致线程在申请锁的时候需要进行不必要的等待,影响性能。

    锁的开销:锁的开销主要包括锁的申请和释放产生的开销,锁可能导致上下文切换,开销主要是处理器时间。

    锁可能导致的问题:

    a.锁泄漏:指一个线程获得锁之后,由于程序的错误,致使该锁一直无法被释放而导致其他线程一直无法获得该锁的现象。

    b.锁的不正当使用还会导致死锁、锁死等线程活性故障

     

    d、锁的作用:

    保护共享数据以实现线程安全,包括保障原子性、可见性和有序性(原子性和可见性>>有序性)

    原子性:锁通过互斥来保障原子性,互斥是指一个锁一次只能被一个线程所持有,所以,临界区代码只能被一个线程执行,即保障了原子性。

    可见性:通过写线程冲刷处理器缓存和读线程刷新处理器缓存实现。获得锁之后,需要刷新处理器缓存,使得前面写线程所做的更新可以同步到本线程。释放锁需要冲刷处理器缓存,使得当前线程对共享数据的改变可以被推送到下一个线程处理器的高速缓冲中。

    有序性:写线程在临界区中所执行的一系列操作在读线程所执行的临界区看起来像是完全按照源代码顺序执行的。但是并不能保证不重排,只是重排不会影响。

     

    锁在保证线程安全的同时满足三大条件,那么我们必须遵守:

    1.线程访问同一组数据的时候必须使用同一个锁

    2.线程中任意的一个线程,即使其仅仅是你读取这组共享数据而没有对其进行更新的话,也需要在读取时持有相应的锁。

     

    锁其实就是把本来并发(未使用锁)的线程改成串行(使用锁)。

     

    e、锁的适用场景

          • check-then-act操作:一个线程读取共享数据并在此基础上决定下个操作是什么。
          • read-modify-write操作:一个线程读取共享数据并在此基础上更新该数据。
          • 多个线程对多个共享数据进行更新:共享数据之间存在关联关系

    2、内存屏障和重入:

    a、线程同步机制的底层助手:内存屏障

    内存屏障:在指令序列中就像一堵墙一样使其两侧的指令无法穿越(即不可以重排序)

    内存屏障在锁中的使用:

          1. 获取锁
          2. 加载屏障
          3. 获取屏障
          4. 临界区
          5. 释放屏障
          6. 存储屏障

    (其中:3和5用来禁止指令重排序)

    Java线程同步机制就是使用内存屏障在具体实现的

     

    b 重入:

    可重入是指对于同一个线程,它可以重新获得已有它占用的锁。 

          • 如果没有重入,当某个线程请求一个由其他线程持有的锁时,发送的请求的线程就会阻塞。
          • 由于内置锁可以重入,因此如果某个线程试图获得一个已经由它持有的锁,那么请求就会成功。

    可重入性:一个线程在持有一个锁的时候可以再次申请该锁

    如何实现可重入性?可重入锁可以被理解为一个对象,该对象包含一个计数器属性,获取锁计数器+1,释放锁计数器-1;

     

    //(比如下面方法,一个同步方法调用另一个同步方法,没有重入的话,等待当前同步结束才能调用新的同步则发生阻塞,重入则避免了这种情况) public class Test { public synchronized f() {} public synchronized g() { // 可以重入,所以当前已经获得锁的线程可以获得f的锁,否则死锁 f(); } }

     

    3、Java虚拟机对锁的实现划分:

    内部锁:通过synchronized关键字实现 独占锁,在高并发访问情况下,可能会引起上下文切换和线程调度

    显示锁:通过java.util.concurrent.locks.Lock接口的实现类实现

     

     

    二、发布和溢出

    我们知道线程安全问题的产生前提是多少线程共享变量,即使是private变量也可能多被个线程共享。

    public class Example{ private Map<String,Integer> reg = new HashMap<String,Integer>(); public void do(){ //reg操作 } }

    如上述代码,多线程多do进行操作时,priavte变量相当于被多个线程共享,就这是发布。当然可以用volatile来保证安全。

     

    1、发布:

      是对象能够在当前作用域之外的代码中使用。可以是以下几种情况:

    1. 一,将对象的引用保存在公有变量或公有静态变量中

    public class Test { public static List<People> list; public Test(){ list = new ArrayList<People>(); } }

    通过list对象可以轻易的遍历list对象中保存的People对象,实际上list对象中保存的People对象也被间接的发布了

     

    二,在一个非私有的方法中返回该引用

    public class Test { private String[] strs = {"AB","BA"}; public String[] getStrs() { return strs; } }

    通过getStrs()方法可以获得本来应该被封装在Test类内部的strs数组

     

    三,将对象引用传递给外部方法

    外部方法:对当前类来说,外部方法是指行为不完全由当前类来规定的方法,包括其他类中定义的方法以及当前类中可以被改写的方法(既不是私有方法,也不是final方法)

    当把一个对象传递给外部方法时,就相当于发布了这个对象

    public class Test { public void get(Object obj){ //obj对象逸出 ... } }

     

    2、逸出:

    某个不应该发布的对象被公布的时候。某个对象逸出后会导致对象的内部状态被暴露,可能危及到封装性,使程序难以维持稳定;若发布尚未构造完成的对象,可能危及线程安全问题。 你必须假设有某个类或线程可能会误用该对象,所以要封装。

    • 不要在构造过程中使this引用逸出。
    • 常见错误:在构造函数中启动一个线程。

     

    最常见的逸出是this引用在构造时逸出,导致this引用逸出的常见错误有:

    1、在构造函数中启动线程:

    当对象在构造函数中显式还是隐式创建线程时,this引用几乎总是被新线程共享,于是新的线程在所属对象完成构造之前就能看见它。

    避免构造函数中启动线程引起的this引用逸出的方法是不要在构造函数中启动新线程,取而代之的是在其他初始化或启动方法中启动对象拥有的线程。

     

    2、在构造方法中调用可覆盖的实例方法:

    在构造方法中调用那些既不是private也不是final的可被子类覆盖的实例方法时,同样导致this引用逸出。

    避免此类错误的方法时千万不要在父类构造方法中调用被子类覆盖的方法。

     

    3、在构造方法中创建内部类:

    在构造方法中创建内部类实例时,内部类的实例包含了对封装实例的隐含引用(深入理解 内部类),可能导致隐式this逸出。例子如下:

    public class ThisEscape { public ThisEscape(EventSource source) { source.registerListener(new EventListener() { public void onEvent(Event e) { doSomething(e); } }); } }

    上述例子中的this逸出可以使用工厂方法来避免,例子如下:

    public class SafeListener { private final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ doSomething(e); } ); } public static SafeListener newInstance(EventSource source) { SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } }

     

     

     

    3、对象的初始化安全:重访final和static:

    安全的发布

    (1)      使用synchronized

    public classSafeLazyInitialization { private static Resource resource; public synchronized static ResourcegetInstance() { if(resource == null) { resource = new Resource(); } return resource; } }

    (2)      使用volatile

    class Foo { private static volatile Helper helper = null;//使用volatile修饰使resource的读操作与写操作具有happen-before规则 public static Helper getHelper() { if (helper == null) synchronized(Foo.class) { if (helper == null) helper = new Helper(); } return helper; } // other functions and members... }

     

    (3)      静态初始化器(利用JVM锁机制提前初始化)

    在初始器中采用了特殊的方式来处理静态域,并提供了额外的线程安全性保证。静态初始化器是由JVM在类的初始化阶段执行即在类被加载后并且在被线程使用之前,由于JVM将在初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,因此在静态初始化期间,内存写入操作将自动对所有线程可见。因此无论是在构造期间还是被引用时,静态初始化的对象都不需要显式的同步。然而,这个规则只适用于构造时的状态,也就是说保证构造的完整性,并不保证之后的操作的线程安全。如果这个类不是线程安全的,仍需要适当的同步。

    public classEagerInitialization{ private static Resource resource = new Resource(); public synchronized static ResourcegetResource() { return resource; } }

     

    (4)      延迟初始化占位

    public classResourceFactory{ private static class ResourceHolder{ private static Resource resource = new Resource(); } public synchronized static ResourcegetResource() { return ResourceHolder.resource; } }

     

    (5) Final

    public class SafeState{ private final Map<String,String> states; public SafeState(){ states = new HashMap<String,String>();//final域写入操作 state.put("a","a");//final域可达的变量,仍然具备可见性,并且不会被重排序到构造函数之后 } }

     

    根据上面所讨论的,可以总结出以下安全发布的常用模式:

    注意, 我们这里的目的是:在对象没有完成构造之前,不可以将其发布,不安全,不推荐(要安全 = 不能随便new一个对象出来)

    不正确的发布可变对象,导致两种错误。

    • 发布线程以外的任何线程都可以看到被发布对象的过期的值,
    • 线程看到的被发布对象的引用是最新的,然而被发布对象的状态是过期的,如果一个对象是可变对象,那么它要被安全发布才可以。

     

     

     

     

    三、线程封闭

      当访问共享的可变数据时,通常需要使用同步。一种避免同步的方式就是不共享数据。

      仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭(Thread Confinement)。

      典型应用:

    ①Swing的可视化组件和数据模型对象都不是线程安全的,Swing通过将它们封闭到Swing的实际分发线程中来实现线程安全;

    ②JDBC的Connection对象。

     

    线程封闭技术:

    ①Ad-hoc线程封闭:维护线程封闭性的职责完全由程序实现来承担。

      在volatile变量上存在一个特殊的线程封闭:能确保只有单个线程对共享的volatile变量执行写入操作(其他线程有读取volatile变量),那么就可以安全地在这些共享的volatile变量上执行“读取-修改-写入”的操作,而其他读取volatile变量的线程也能看到最新的值。

     

    ②栈封闭:在栈封闭中,只能通过局部变量才能访问对象。

    public int loadTheArk(Collection<Animal> candidates){ SortedSet<Animal> animals; int numPairs = 0; Animal candidate = null; //animals被限制在本地方法栈中 animals = new TreeSet<Animal>(new SpeciesGenderComparator()); animals.addAll(candidates); for(Animal a : animals){ if(candidate == null || !candidate.isPotentialMate(a)){ candidate = a; }else{ ark.load(new AnimalPair(candidate, a)); ++numPairs; candidate = null; } } return numPairs; } 指向TreeSet对象的唯一引用保存在animals中,而animals这个引用被封闭在局部变量中,因此封闭在线程本身的工作内存中,其它线程不能访问。 如果发布了对集合的引用,那么线程的封闭性将被破坏,并且导致对象animals的逸出。

      

    ③ThreadLocal类:这是线程封闭的更规范方法,这个类能使线程中的某个值与保存值的对象关联起来。

      提供get()和set()方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。

      ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。怎么理解呢?还是JDBC的Connection对象,防止共享,所以通常将JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接。

     

    展开全文
  • windows系统多线程同步机制原理总结

    千次阅读 2018-12-24 21:24:33
    windows系统多线程同步机制原理总结 同步问题是开发过程中遇到的重要问题之一。同步是要保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。 为了保证多线程...

    windows系统多线程同步机制原理总结

    同步问题是开发过程中遇到的重要问题之一。同步是要保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。
    为了保证多线程间的同步,Windows操作系统提供了一系列的机制:事件、互斥体、信号量等等。本文主要基于对《Windows内核原理与实现》一书相关章节的整理并结合自己的理解介绍同步机制的大概实现原理,有任何不妥的地方,希望大家能够不吝指出。

    Windows线程调度

    我们先来简单的说以下Windows的线程调度,这是理解后续东西的基础。Windows的调度算法是一个抢占式的、支持多处理器的优先级调度算法。这种调度算法将处理器的时间分成一个个的时间片段,每个时间片段分给不同的线程去执行线程中的指令,直到时间片用完。显然,这样做使得处理器的执行不是按照一个事先知道的顺序依次进行;操作系统在调度线程时需要一个时钟中断来获得对处理器的控制权,从而引导处理器去执行操作系统想要它执行的目标线程的指令。

    中断

    说到Windows的线程调度是通过中断来切换线程的,那么到底什么是中断呢?我们这样想:处理器在执行指令时肯定是顺序执行指令流,那么它怎么处理一些事先无法预知、运行时才发生的事件呢?比如用户某一时刻突然敲下键盘。一种办法是处理器足够频繁地去挨个检查所有可能发生的事件是否发生了,这种方法显然很笨;另一种方法就是使用中断。
    中断其实包含硬件中断和软件中断,说说硬件中断(软件中断其实是模拟的硬件中断)。硬件中断是外部设备在需要通知处理器去做一件事的时候向处理器的特定管脚(NMI和INTR)发送数据,处理器每执行完一条指令都会去检查这个管脚的状态,看看是否有中断发生。通过中断机制,处理器就不需要去挨个设备的检查了,只需要统一的检查是否发生了中断。那么,处理器在得知发生中断后,又怎么知道发生了什么中断?以及怎么去处理中断呢?
    原来,每个中断都有一个中断编号,也称为中断向量。外部设备在触发处理器中断时会发生相应的中断向量。除此之外,操作系统中维护一个中断描述符表(IDT),这个表将每个中断向量与中断服务例程(用来处理该中断的一段程序)关联起来。处理器根据中断向量查询IDT,从而找到中断服务例程的地址,去执行中断服务例程,完成对中断的响应。

    同步机制的实现

    现在我们明白了操作系统是怎么调度线程的了,这是这种调度方式使得线程的同步有了很多不同的方式。那怎么实现呢?

    1. 不依赖于线程调度的同步机制

    第一种做法是对中断下手。需要申明,这种方法只对单处理器有效。单处理器情况下,导致资源争用的罪魁祸首是线程调度,它使得一个处理器分时并行地干几件事,就好像有多个处理器一样。那不让线程调度不就行了?对!前面说到了,线程调度是依靠中断实现的,那就从中断下手。处理器在处理中断时,并不是什么中断都处理,它会判断当前条件下是否需要处理某一中断。而Windows提供了一套中断级别定义方案,叫做中断请求级别(IRQL)。它使用0~31来表示优先级,数值越大,优先级越高;处理器任何时候都运行在一个级别上,且只能被更高级别的中断打断。感兴趣的同学可以去查查IRQL到底包含哪些级别,本文只说其中一个级别——DISPATCH_LEVEL,从名称中可以大致猜到这是线程调度时所在的级别。因此,如果在访问需要同步的资源之前,将处理器的运行级别提高到DISPATCH_LEVEL或更高的IRQL,这时操作系统就不会调度线程了,对单处理器而言就不存在其他线程同时访问资源的情况,也就实现了目标。

    下面划重点:自旋锁我们都知道。它的实现就利用了这种方式,因此,线程在自旋等待时不会有线程切换。也正是因为不会有线程切换,省去了切换的耗时,因此它很适合预期等待时间很短的情况使用。

    2. 基于线程调度的同步机制

    第二种方法我们不去干涉线程调度。那么要想让不同线程能够协调起来,我们需要一个全局的东西去协调它们。这个东西就是同步对象,也称为分发器对象。无论是互斥体还是信号量还是其他什么同步方式,它们都需要针对一个确定的分发器对象。一个分发器对象有基本的两个状态:有信号和无信号状态,分发器对象初始化时为有信号状态。当一个线程执行某种同步方式时,它去查看指定分发器对象是否有信号,若有信号,该线程继续执行,同时该分发器对象的状态变为无信号;再有别的线程去查看该分发器对象时,发现状态为无信号,此时该线程进入等待状态(睡眠),操作系统的线程调度将不再调度该线程。这就实现了只能一个线程访问同一资源。

    那么,当第一个线程执行完了后,分发器对象的状态重新设定为有信号后,其他在等待该分发器对象的线程又怎么知道呢?这就涉及到线程对象和分发器对象的数据结构。在线程对象和分发器对象中都有同样的一个数据成员:一个指向某一链表头节点的指针,而这个链表是一个等待块对象链表。每个等待块对象都记录了哪个线程在等待哪个对象。是的,等待块对象会同时加入到两个链表中:一个是线程对象中的链表,一个是分发器对象中的链表。
    现在我们回到之前的情形,当别的线程去查看该分发器对象时,发现状态为无信号,此时实例化一个等待块对象,它记录了当前线程在等当前分发器对象,这个等待块对象都添加到了线程对象和分发器对象的链表中;当分发器对象重新变为有信号后,它会去唤醒等待块链表中记录的线程对象,此时等待的线程的状态变成延迟的就绪状态,等待操作系统线程调度器来调度。

    分发器对象有很多种,具体可分为:事件、突变体、信号量、进程、线程、队列、门、定时器。有些名字听起来是不是很熟悉?是的,它们都对应着不同的同步方式:比如事件就对应同步中的事件、突变体对应互斥体…这些分发器对象在数据结构上分为两部分:对象头部和对象体。不同分发器的对象头部是一样的(对象头部的第三个数据正是等待块链表的头指针),对象体因功能不同而不同(类似于面向对象里的多态性)。对象体不同,正是与之对应的同步方式功能有所不同的本质原因。比如说突变体对象的对象体中有一个所有者概念,用于表示当前拥有该突变体对象的线程(我认为这就是互斥锁能作为递归锁的原因)。

    需要说一下,这些同步方式都需要处理器提供支持。如果处理器不提供原子运算的功能,那么什么事件、信号量等等都是空谈,因为至少在对分发器对象进行判断的时候必须要保证其过程是原子操作。现代处理器会提供一些原子运算指令,比如在Inter x86指令体系中,有些运算指令加上lock前缀就可以保证其原子性,lock前缀指令使用的两个条件:

    1. 指令的目标操作数必须一个是内存操作数;
    2. 仅适用于ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、DEC、INC、NEG、NOT、OR、SBB、SUB、XOR、XADD、XCHG。

    最后总结一下,我们常见的线程同步方式,如事件、互斥体等,它们的实现都是依靠于分发器对象。操作系统通过查看分发器对象的状态,判断一个线程是等待还是执行。

    展开全文
  • 【Java多线程】线程同步机制

    千次阅读 2018-06-08 18:11:41
    线程同步是为了确保线程安全,所谓线程安全指的是多个线程对同一资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的。如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与...

    线程同步是为了确保线程安全,所谓线程安全指的是多个线程对同一资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的。如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与预期值一样,则是线程安全的。

    Java中与线程同步有关的关键字/类包括:

    volatile、synchronized、Lock、AtomicInteger等concurrent包下的原子类。。。等

    接下来讨论这几种同步方法。

    volatile

    volatile一般用在多个线程访问同一个变量时,对该变量进行唯一性约束,volatile保证了变量的可见性,不能保证原子性。

    用法(例):private volatile booleanflag = false

    保证变量的可见性:volatile本质是告诉JVM当前变量在线程寄存器(工作内存)中的值是不确定的,需要从主存中读取,每个线程对该变量的修改是可见的,当有线程修改该变量时,会立即同步到主存中,其他线程读取的是修改后的最新值。

    不能保证原子性:原子性指的是不会被线程调度机制打断的操作,在java中,对基本数据类型的变量的读取和赋值操作是原子性操作。自增/自减操作不是原子性操作。例如:i++,其实是分成三步来操作的:1)从主存中读取i的值;2)执行+1操作;3)回写i的值。volatile关键字并不能保证原子性操作。非原子操作都会产生线程安全的问题,那么如何实现自增/自减的原子性呢?后续将有讲解。

    synchronized

    synchronized提供了一种独占的加锁方式,是比较常用的线程同步的关键字,一般在“线程安全的单例”中普遍使用。该关键字能够保证代码块的同步性和方法层面的同步。

    用法(例):1)代码块同步

    //使用synchronized关键字实现线程安全的单例模式
        private static Singleton instance;
        public static Singleton getInstance(){
            if(instance == null){
                synchronized (Singleton.class)
                {
                    if(instance == null){
                        instance = new Singleton();
                    }              
                }
            }
            return instance;
    }
    privateSingleton(){ }

    2)方法同步

    public static synchronized Singleton getInstance2(){
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    private Singleton(){ }

    很多人说synchronized在性能上存在较大问题,但并没有真实环境产生的数据比较说明,因此在这里不好讨论性能问题。

    volatile和synchronized的区别

    1)      volatile通过变量的可见性,指定线程必须从主存中读取变量的最新值;synchronized通过阻塞线程的方式,只有当前线程能访问该变量,锁定了当前变量。

    2)      volatile使用在变量级别;synchronized可以使用在变量、方法、类级别

    3)      volatile不会造成线程阻塞;synchronized可能会造成线程阻塞

    4)      volatile不能保证原子性;synchronized能保证原子性

    5)      volatile标记的变量不会被编译器优化;synchronized标记的变量有可能会被编译器优化(指令重排)。

    如何保证自增/自减的原子性

    1)      使用java.util.concurrent包下提供的原子类,如AtomicInteger、AtomicLong、AtomicReference等。

    用法:   

    AtomicInteger atomicInteger = new AtomicInteger();
    atomicInteger.getAndIncrement();//实现原子自增
    atomicInteger.getAndDecrement();//实现原子自减

    2)使用synchronized同步代码块

    Synchronized(this){
          value++;
    }

    3)使用Lock显示锁同步代码块

     private Lock lock = new ReentrantLock();
        private final int incrementAndGet(){
            lock.lock();
            try
            {
                return value++;
            }
            finally
            {
                // TODO: handle finally clause
                lock.unlock();
            }
        }



    展开全文
  • c++ 线程同步机制

    千次阅读 2017-03-01 16:23:25
    c++线程同步

    互斥量

    在使用互斥量的时候,最好使用RAII进行封装,使用非递归的互斥量,尽量在一个函数内进行lock、unlock。

    常有的互斥量对象,简单的互斥对象std::mutex,带有超时机制的互斥对象std::timed_mutex,一般使用RAII来避免死锁的情况。

    std::lock_guard,对象生存期内是不允许手动加锁解锁的。构造时可选是否加锁(不加锁时假定当前线程已经获得锁的所有权),析构时自动释放锁,所有权不可转移。

    std::unique_lock,对象生存期可以进行手动加锁解锁。比lock_guard更加灵活。


    lock_guard只支持std::lock和std::adopt_lock。

    unique_lock则支持:std:lock、std::defer_lock、std::try_lock、std::adopt_lock。

    defer_lock 不取得互斥量的所有权;当多个线程都使用同样N个互斥量的时候,必须保证其加锁的顺序是一致的这种情况下使用try_lock更好。

    try_lock 则会在没有阻塞的时候取得互斥量的所有权;其变种try_lock_for(duration)和try_lock_until(timepoint)。

    adopt_lock  即使互斥量已经被另外的线程加锁,也会夺取互斥量的所有权进而在该线程加锁。



    条件变量

    条件变量是一个或多个线程等待某个布尔表达式为真,即等待别的线程“唤醒”它。

    对于wait()端,为了防止虚假唤醒,必须配合锁一起使用:

    1. 必须与mutex 一起使用,该布尔表达式的读写需受此mutex 保护

    2. mutex 已上锁的时候才能调用wait()

    3. 把判断布尔条件和wait() 放到while 循环中

    示例:

    MutexLock mutex;
    Condition cond(mutex);
    std::deque<int> queue;
    
    int dequeue()
    {
      MutexLockGuard lock(mutex);
      while (queue.empty()) {  // 必须用循环;必须在判断之后再 wait()
        cond.wait(); // 这一步会原子地 unlock mutex 并进入 blocking,不会与 enqueue 死锁
      }
      assert(!queue.empty());
      int top = queue.front();
      queue.pop_front();
      return top;
    }

    对于 signal/broadcast 端:

    1. 不一定要在mutex 已上锁的情况下调用signal 

    2. signal 之前一般要修改布尔表达式

    3. 修改布尔表达式通常要用mutex保护

    void enqueue(int x)
    {
      MutexLockGuard lock(mutex);
      queue.push_back(x);
      cond.notify();
    }

    实现了一个简单的unbounded BlockingQueue

    条件变量是较底层的同步原语,很少直接使用,一般都是用它来实现高层的同步措施,如 BlockingQueue CountDownLatch


    临界区

    临界区是值一个访问共享资源的代码段。当有一个线程访问了临界区之后,其他线程想要访问临界区时会被挂起,直到进入临界区的线程离开。windows API提供了临界区对象结构体CRITICAL_SECTION,常用API有:

    1. 申请一个临界区变量  CRITICAL_SECTION gSection;

    2.InitializeCriticalSection(&gSection),初始化临界区,唯一的参数是指向结构体CRITICAL_SECTION的指针变量(LPCRITICAL_SECTION lpCriticalSection)。

    3.EnterCriticalSection(&gSection),线程进入已经初始化的临界区,并拥有该临界区的所有权。这是一个阻塞函数,如果线程获得临界区的所有权成功,则该函数将返回,调用线程继续执行,否则该函数将一直等待,这样会造成该函数的调用线程也一直等待。如果不想让调用线程等待(非阻塞),则应该使用TryEnterCriticalSection(&gSection)。

    4.LeaveCriticalSection(&gSection),线程离开临界区并释放对该临界区的所有权,以便让其他线程也获得访问该共享资源的机会。一定要在程序不适用临界区时调用该函数释放临界区所有权,否则程序将一直等待造成程序假死。

    5.DeleteCriticalSection(&gSection),该函数的作用是删除程序中已经被初始化的临界区。如果函数调用成功,则程序会将内存中的临界区删除,防止出现内存错误。


    未完待续。。。

    展开全文
  •   1、确保线程互斥访问同步代码;   2、保证共享变量的修改能够及时可见;   3、有效解决指令重排序问题。   synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层...
  • 本文档系操作系统课程线程同步机制的实验报告,实验内容包括:无同步机制、调用Mutex互斥变量、使用peterson软件法实现线程同步。完整的cpp源代码见文档附录。
  • 线程同步机制 (Windows)   线程的同步机制: 1、 Event 用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动...
  • 线程同步:  当多个控制线程共享相同的内存时,需要确保每个进程看到一致的数据视图。同一个数据如果被两个及以上的线程进行同时访问操作的时候,有可能就会造成数据不一致的现象。为了解决这个问题,线程不
  • PAGE PAGE 18 目 录 TOC \o "1-3" \h \z \u JAVA多线程同步机制及其应用 1 1 前言 2 1.1 线程 2 1.2 同步机制 2 2 JAVA多线程 3 2.1 线程的创建 3 2.1.1 继承Thread类实现多线程 3 2.1.2 实现Runnable接口实现多线程...
  • JAVA多线程——线程同步机制,同步方法和同步块 并发:同一个对象被多个线程同时操作 线程同步:处理多线程问题时,多个线程访问同一个对象,并且某个对象还想修改这个线程。这时候就需要线程同步。线程同步其实就是...
  • 为了解决多线程访问公共资源的冲突,开发人员需要采用线程同步机制,来避免同一时刻多个线程访问公共资源;该程序使用事件对象进行线程同步;事件对象分为人工重置事件对象和自动重置事件对象。
  • 上篇介绍线程,说到线程同步机制,其中说到加锁的机制,如果加锁不合理,则会产生“死锁”。如果加锁的位置合理,则会解决多线程访问同一数据的问题。多线程访问的问题,其中很典型的一个模型就是生产者/消费者模型...
  • java的线程同步机制synchronized关键字的理解java的线程同步机制synchronized关键字的理解java的线程同步机制synchronized关键字的理解java的线程同步机制synchronized关键字的理解java的线程同步机制synchronized...
  • 四种多线程同步机制

    千次阅读 2013-04-06 18:16:34
    1) 因为Critical Sections不是内核对象,所以只能用来同一进程内线程间的同步,不能用来多个不同进程间的线程同步。 2) 如果在Critical Sections中间突然程序crash或是exit而没有调用LeaveCriticalSection,则...
  • 一、互斥量(mutex) ...如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次等待它重
  • 小实验一:编写一个没有线程同步机制的程序,调试程序,观察在执行程序的过程中,出现的问题并解答原因 小实验二:使用Windows互斥信号量操作函数解决上述线程并发问题,并分析、尝试和讨论线程执行体中有关信号量...
  • 前言:相信大家在进行Java开发的时候经常会接触到同步的概念,在多线程并发的情况下,为保证同一个时间点只能被一个线程访问到,就需要用到同步机制。想要了解更多关于Java多线程知识,请移步:Android多线程机制...
  • 继续讨论多窗口卖票问题中的线程安全问题: 方式二: 一、问题: 出现了线程安全问题 ? 二、问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程...
  • 线程同步机制的几种方法

    万次阅读 2013-03-20 22:17:35
    Critical Section Critical section(临界区)用来实现“排他性占有”。适用范围是单一进程 的各线程之间。它是:  一个局部性对象,不是一个核心...可以实现线程间互斥,不能用来实现同步。 Semaphore Sema
  • 实现操作系统的线程同步机制,包含代码,能运行。
  • 四种进程线程同步机制

    千次阅读 2011-10-06 00:15:32
    现在流行的进程线程同步互斥的控制机制,其实是由最原始最基本的4种方法实现的: 1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 2.互斥量:为协调共同对一个共享资源的...
  • java的线程同步机制中的同步,与操作系统中进程同步的同步,两个意义是一样的吗?一样的话,为什么java解决线程安全问题使用的是同步机制而不是互斥机制。 如果我没理解错的话,进程同步是为了解决直接制约问题,也...
  • 设计一个多线程, 并且实现同步, 我理解的多线程需求如下: 1. 线程在Java端启动, 两个线程都调用C的方法 2. 有一个共同的数据, 被C的代码修改, 要求线程能对这个修改做同步, 即线程1
  • Linux的completion线程同步机制

    千次阅读 2021-08-30 11:21:10
    Linux的completion线程同步机制 当Linux一个线程需要对某个硬件设备进行读写访问或者控制操作时,该线程通过系统调用执行的内核读写访问或控制操作函数如果要确保上次操作完成后再进行下一步操作,即等待硬件设备...
  • 进程同步及线程同步的几种机制

    万次阅读 2018-09-14 09:09:35
    进程中线程同步的四种常用方式: 1. 互斥量: 采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。 2. 信号量: 它允许同一...
  • 之前有写过类似的博客,这东西不用老忘,现在又有更清晰的理解了。 ... 当值=1时,信号量可用,当值=0时候,信号量不可用,这里就可以利用P()V()操作来实现,如果=0时候,线程阻塞在P()操作 (2)
  • 线程同步机制的几种方法总结与对比 需要线程同步的原因: 当有多个线程同时访问一个共享内存里面的变量时,有时会出现一个线程正在修改该变量的值,而其他的线程正在读取数据,可能就会导致错误。   实现线程...
  • python的多线程及线程同步方式

    千次阅读 2019-07-17 09:37:23
    1.线程执行 join与setDaemon 1.子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),主线程运行结束子线程即结束; 2 .如果join()线程,那么主线程会等待子线程执行完再执行...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 426,007
精华内容 170,402
关键字:

线程同步机制