首页 > 编程语言 >并发编程 ---- 信号量线程同步

并发编程 ---- 信号量线程同步

时间:2023-07-19 20:57:43浏览次数:44  
标签:---- Console 信号量 线程 事件 信号 new WaitHandle

合集 - c#基础(6)   1.编码技巧 --- 如何实现字符串运算表达式的计算07-122.编码技巧 --- 同步锁对象的选定07-133.解读 --- yield 关键字07-17 4.并发编程 --- 信号量线程同步07-18 5.并发编程 ---为何要线程池化07-186.编码技巧 --- 谨防闭包陷阱07-19 收起  

引言

上文编码技巧 --- 同步锁对象的选定中,提到了在C#中,让线程同步有两种方式:

  • 锁(lock、Monitor等)
  • 信号量(EventWaitHandle、Semaphore、Mutex)

加锁是最常用的线程同步的方法,就不再讨论,本篇主要讨论使用信号量同步线程。

WaitHandle介绍

实际上,再C#中 EventWaitHandleSemaphoreMutex 都是抽象类 WaitHandle 的派生类,它提供了一组等待信号的方法和属性。如下图:

image.png

主要包含静态方法 SignalAndWait()WaitAll()WaitAny()及一个虚方法WaitOne()。下面介绍一个这几个方法。

介绍这些方法之前,先简单介绍一下 WaitHandle 的派生类 EventWaitHandle,该派生类有两个实现类 AutoResetEventManualResetEvent,其方法列表如下:
image.png

重点说一下,Set()Reset():

  • Set()方法设置事件为有信号状态:当调用 Set() 时,它将被设置为终止状态,并允许一个或多个等待该事件的线程继续执行。
  • Reset()方法设置事件为无信号状态:当调用 Reset() 时,它将被设置为非终止状态,所有想要等待该事件的线程都将被阻塞,直到调用 Set() 方法使其变为终止状态。

注意:这里的有信号,无信号的意思类似于红绿灯,有信号你才能够通行,对于线程来说,有信号意味着可以接着往下运行,无信号则阻塞等待信号。

接下来的代码段演示皆使用 AutoResetEvent 进行演示。

SignalAndWait()

当调用 WaitHandle 的静态方法 SignalAndWait() 时,会使得当前线程等待一个 WaitHandle 对象的信号,同时设置另一个 WaitHandle 对象为有信号状态。当第一个 WaitHandle 对象收到信号时,当前线程继续执行,同时第二个 WaitHandle 对象变为无信号状态。

static AutoResetEvent event1 = new AutoResetEvent(false);
static AutoResetEvent event2 = new AutoResetEvent(false);

static void Main(string[] args)
{
    Thread t1 = new Thread(new ThreadStart(Worker1));
    Thread t2 = new Thread(new ThreadStart(Worker2));

    t1.Start();
    t2.Start();

    Console.ReadLine();
}

static void Worker1()
{
    Console.WriteLine("线程1开始执行……");

    event1.WaitOne(); // 等待事件1的发生

    Console.WriteLine("线程1收到事件1的信号,继续执行……");

    WaitHandle.SignalAndWait(event1, event2); // 发送事件2的信号并等待事件2的发生

    Console.WriteLine("线程1收到事件2的信号,继续执行……");
}

static void Worker2()
{
    Console.WriteLine("线程2开始执行……");

    Thread.Sleep(2000); // 模拟线程2的执行时间

    Console.WriteLine("线程2发出事件1的信号……");

    event1.Set(); // 发送事件1的信号

    Thread.Sleep(2000); // 模拟线程2的执行时间

    Console.WriteLine("线程2发出事件2的信号……");

    WaitHandle.SignalAndWait(event2, event1); // 发送事件1的信号并等待事件1的发生

    Console.WriteLine("线程2收到事件1的信号,继续执行……");
}

输出:

线程1开始执行……
线程2开始执行……
线程2发出事件1的信号……
线程1收到事件1的信号,继续执行……
线程2发出事件2的信号……
线程2收到事件1的信号,继续执行……
线程1收到事件2的信号,继续执行……

WaitAll()

当调用 WaitHandle 的静态方法 WaitAll() 时,它可以等待多个WaitHandle对象的信号,直到所有对象都收到信号或等待超时。

static AutoResetEvent[] events = new AutoResetEvent[3]
{
    new AutoResetEvent(false),
    new AutoResetEvent(false),
    new AutoResetEvent(false)
};

static void Main(string[] args)
{
    Thread t1 = new Thread(new ThreadStart(Worker1));
    Thread t2 = new Thread(new ThreadStart(Worker2));

    t1.Start();
    t2.Start();

    Console.ReadLine();
}

static void Worker1()
{
    Console.WriteLine("线程1开始执行……");

    WaitHandle.WaitAll(events); // 等待所有事件的发生

    Console.WriteLine("线程1收到所有事件的信号,继续执行……");
}

static void Worker2()
{
    Console.WriteLine("线程2开始执行……");

    Thread.Sleep(2000); // 模拟线程2的执行时间

    Console.WriteLine("线程2发出事件1的信号……");

    events[0].Set(); // 发送事件1的信号

    Thread.Sleep(2000); // 模拟线程2的执行时间

    Console.WriteLine("线程2发出事件2的信号……");

    events[1].Set(); // 发送事件2的信号

    Thread.Sleep(2000); // 模拟线程2的执行时间

    Console.WriteLine("线程2发出事件3的信号……");

    events[2].Set(); // 发送事件3的信号
}

输出:

线程1开始执行……
线程2开始执行……
线程2发出事件1的信号……
线程2发出事件2的信号……
线程2发出事件3的信号……
线程1收到所有事件的信号,继续执行……

WaitAny()

当调用 WaitHandle 的静态方法 WaitAny() 时,它可以等待多个WaitHandle对象中的任意一个对象收到信号,直到有一个对象收到信号或等待超时。

static AutoResetEvent[] events = new AutoResetEvent[3]
{
    new AutoResetEvent(false),
    new AutoResetEvent(false),
    new AutoResetEvent(false)
};

static void Main(string[] args)
{
    Thread t1 = new Thread(new ThreadStart(Worker1));
    Thread t2 = new Thread(new ThreadStart(Worker2));

    t1.Start();
    t2.Start();

    Console.ReadLine();
}

static void Worker1()
{
    Console.WriteLine("线程1开始执行……");

    WaitHandle.WaitAny(events); // 等待任意事件的发生

    Console.WriteLine("线程1收到任意事件的信号,继续执行……");
}

static void Worker2()
{
    Console.WriteLine("线程2开始执行……");

    var randomIndex = new Random().Next(0, 2);

    Console.WriteLine("线程2发出任意一个事件的信号……");

    events[randomIndex].Set(); //发送任意一个事件的信号
}

输出:

线程1开始执行……
线程2开始执行……
线程2发出任意一个事件的信号……
线程1收到任意事件的信号,继续执行……

WaitOne()

WaitOne()方法上文中其实已经用到了,它就表示阻塞当前线程,等待当前 WaitHandle 对象收到信号,直到对象收到信号或等待超时。如果WaitHandle对象收到信号,WaitOne()方法返回true,否则返回false。使用简单就不在贴代码段。

派生类的异同

上面已经提到了EventWaitHandleSemaphoreMutex 都是抽象类 WaitHandle 的派生类,它们的作用类似,但在使用和实现上有一些不同。下面我们来简单介绍下它们的异同点。

  1. EventWaitHandle:

    EventWaitHandle 有两种类型:AutoResetEventManualResetEvent。它们的区别在于AutoResetEvent 在有信号时只通知一个等待线程,而 ManualResetEvent 在有信号时通知所有等待线程。
    两者设置为终止状态的方式都是调用 Set() 方法。

  2. Semaphore

    Semaphore 可以用于多个线程之间的资源控制。Semaphore 可以控制同时访问共享资源的线程数量。设置为终止状态的方式是调用 Release() 方法。

  3. Mutex

    Mutex 可以用于多个线程之间的互斥访问共享资源。Mutex 可以保证同一时间只有一个线程可以访问共享资源。设置为终止状态的方式是调用 ReleaseMutex() 方法。

 

 

出处:https://www.cnblogs.com/pandefu/p/17536279.html

标签:----,Console,信号量,线程,事件,信号,new,WaitHandle
From: https://www.cnblogs.com/mq0036/p/17566699.html

相关文章

  • Android 妙用TextView实现左边文字,右边图片
    原文:Android妙用TextView实现左边文字,右边图片-Stars-One的杂货小窝有时候,需要文字在左边,右边有个箭头,我个人之前会有两种做法:使用线性布局来实现或者使用约束布局,一个左对齐,一个右对齐这几天突然想到是否可以使用TextView的设置图标的方式实现,研究发现确实可......
  • 并发编程 ----为何要线程池化
    合集-c#基础(6) 1.编码技巧---如何实现字符串运算表达式的计算07-122.编码技巧---同步锁对象的选定07-133.解读---yield关键字07-174.并发编程---信号量线程同步07-185.并发编程---为何要线程池化07-186.编码技巧---谨防闭包陷阱07-19收起 引言众......
  • 函数式编程-part1概述和理解
    为什么学?能够看懂公司里的代码大数量下处理集合效率高,因为有并行流,而自己创建处理会有很多问题代码可读性高消灭嵌套地狱本系列将从Lambda表达式、Stream流、Optional、函数式接口、方法引用等顺序开始讲解//查询未成年作家的评分在70以上的书籍由于洋流影响所......
  • 属性
          ......
  • Eigen库操作
    #include<iostream>#include<eigen3/Eigen/Dense>usingnamespacestd;usingnamespaceEigen;intmain(){Matrix2fss;ss<<2.3f,3.2f,3.4f,3.1f;cout<<ss<<endl;cout<<"======="<<endl;......
  • 编码技巧 --- 谨防C#闭包陷阱
    合集-c#基础(6) 1.编码技巧---如何实现字符串运算表达式的计算07-122.编码技巧---同步锁对象的选定07-133.解读---yield关键字07-174.并发编程---信号量线程同步07-185.并发编程---为何要线程池化07-186.编码技巧---谨防闭包陷阱07-19收起 引言先......
  • P4136 谁能赢呢?
    题目描述小明和小红经常玩一个博弈游戏。给定一个n×n的棋盘,一个石头被放在棋盘的左上角。他们轮流移动石头。每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问过。谁不能移动石头了就算输。假如小明先移动石头,而且两个选手都以最优策略......
  • springboot整合mybatis
    项目结构: 1.添加依赖<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://mave......
  • c语言学习7
    函数传参1、函数中定义的变量属于该函数,出了该函数就不能再被别的函数直接使用2、实参与形参之间是以赋值的方式进行传递数据的,并且是单向值传递3、return语句其实是把返回值数据放入公共区域内存中(调用者和被调用者都可以访问),调用者会从该区域获取返回值;如果不写return语句,......
  • Day07_2.深浅copy之深copy
    使用场景:如果需要拷贝一个列表,并且让两个列表的改操作完全独立开,就用深copy,否则就用浅copy深拷贝:把可变和不可变类型做了区分对待,不可变类型的指向的还是原来的值id不变,可变类型指向的是新的值id改变1.深拷贝在拷贝原列表后会产生一个新列表id不相同: 2.深拷贝可变类型数据内的......