C#并行编程之信号量

 更新时间:2022年05月09日 14:47:52   作者:springsnow  
这篇文章介绍了C#并行编程之信号量,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一:CountdownEvent

这种采用信号状态的同步基元非常适合在动态的fork,join的场景,它采用“信号计数”的方式,就比如这样,一个麻将桌只能容纳4个人打麻将,如果后来的人也想搓一把碰碰运气,那么他必须等待直到麻将桌上的人走掉一位。好,这就是简单的信号计数机制,从技术角度上来说它是定义了最多能够进入关键代码的线程数。

但是CountdownEvent更牛X之处在于我们可以动态的改变“信号计数”的大小,比如一会儿能够容纳8个线程,一下又4个,一下又10个,这样做有什么好处呢?还是承接上一篇文章所说的,比如一个任务需要加载1w条数据,那么可能出现这种情况。

加载User表:根据user表的数据量,我们需要开5个task。

加载Product表:产品表数据相对比较多,计算之后需要开8个task。

加载order表:由于我的网站订单丰富,计算之后需要开12个task。

先前的文章也说了,我们需要协调task在多阶段加载数据的同步问题,那么如何应对这里的5,8,12,幸好,CountdownEvent给我们提供了可以动态修改的解决方案。

我们看到有两个主要方法:Wait和Signal。每调用一次Signal相当于麻将桌上走了一个人,直到所有人都搓过麻将wait才给放行,这里同样要注意也就是“超时“问题的存在性,尤其是在并行计算中,轻量级别给我们提供了”取消标记“的机制,这是在重量级别中不存在的,比如下面的重载public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken),具体使用可以看前一篇文章的介绍。

//默认的容纳大小为“硬件线程“数
static CountdownEvent cde = new CountdownEvent(Environment.ProcessorCount);

static void Main(string[] args)
{
    //加载User表需要5个任务
    var userTaskCount = 5;

    //重置信号
    cde.Reset(userTaskCount);

    for (int i = 0; i < userTaskCount; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            LoadUser(obj);
        }, i);
    }

    //等待所有任务执行完毕
    cde.Wait();

    Console.WriteLine("\nUser表数据全部加载完毕!\n");

    //加载product需要8个任务
    var productTaskCount = 8;

    //重置信号
    cde.Reset(productTaskCount);

    for (int i = 0; i < productTaskCount; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            LoadProduct(obj);
        }, i);
    }

    cde.Wait();

    Console.WriteLine("\nProduct表数据全部加载完毕!\n");

    //加载order需要12个任务
    var orderTaskCount = 12;

    //重置信号
    cde.Reset(orderTaskCount);

    for (int i = 0; i < orderTaskCount; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            LoadOrder(obj);
        }, i);
    }

    cde.Wait();

    Console.WriteLine("\nOrder表数据全部加载完毕!\n");

    Console.WriteLine("\n(*^__^*) 嘻嘻,恭喜你,数据全部加载完毕\n");

    Console.Read();
}

static void LoadUser(object obj)
{
    try
    {
        Console.WriteLine("当前任务:{0}正在加载User部分数据!", obj);
    }
    finally
    {
        cde.Signal();
    }
}

static void LoadProduct(object obj)
{
    try
    {
        Console.WriteLine("当前任务:{0}正在加载Product部分数据!", obj);
    }
    finally
    {
        cde.Signal();
    }
}

static void LoadOrder(object obj)
{
    try
    {
        Console.WriteLine("当前任务:{0}正在加载Order部分数据!", obj);
    }
    finally
    {
        cde.Signal();
    }
}

二:SemaphoreSlim

在.net 4.0之前,framework中有一个重量级的Semaphore,人家可以跨进程同步,咋轻量级不行,msdn对它的解释为:限制可同时访问某一资源或资源池的线程数。关于它的重量级demo,我的上一个系列有演示,你也可以理解为CountdownEvent是 SemaphoreSlim的功能加强版,好了,举一个轻量级使用的例子。

static SemaphoreSlim slim = new SemaphoreSlim(Environment.ProcessorCount, 12);

static void Main(string[] args)
{
    for (int i = 0; i < 12; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            Run(obj);
        }, i);
    }

    Console.Read();
}

static void Run(object obj)
{
    slim.Wait();

    Console.WriteLine("当前时间:{0}任务 {1}已经进入。", DateTime.Now, obj);

    //这里busy3s中
    Thread.Sleep(3000);

    slim.Release();
}

同样,防止死锁的情况,我们需要知道”超时和取消标记“的解决方案,像SemaphoreSlim这种定死的”线程请求范围“,其实是降低了扩展性,所以说,试水有风险,使用需谨慎,在觉得有必要的时候使用它。

三: ManualResetEventSlim

相信它的重量级别大家都知道是ManualReset,而这个轻量级别采用的是"自旋等待“+”内核等待“,也就是说先采用”自旋等待的方式“等待,直到另一个任务调用set方法来释放它。如果迟迟等不到释放,那么任务就会进入基于内核的等待,所以说如果我们知道等待的时间比较短,采用轻量级的版本会具有更好的性能,原理大概就这样,下面举个小例子。

//2047:自旋的次数
 static ManualResetEventSlim mrs = new ManualResetEventSlim(false, 2047);

 static void Main(string[] args)
 {

     for (int i = 0; i < 12; i++)
     {
         Task.Factory.StartNew((obj) =>
         {
             Run(obj);
         }, i);
     }

     Console.WriteLine("当前时间:{0}我是主线程{1},你们这些任务都等2s执行吧:\n",
     DateTime.Now,
     Thread.CurrentThread.ManagedThreadId);
     Thread.Sleep(2000);

     mrs.Set();
 }

 static void Run(object obj)
 {
     mrs.Wait();

     Console.WriteLine("当前时间:{0}任务 {1}已经进入。", DateTime.Now, obj);
 }

到此这篇关于C#并行编程之信号量的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • winform把Office转成PDF文件

    winform把Office转成PDF文件

    这篇文章介绍了winform把Office转成PDF文件的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • C#中foreach原理以及模拟的实现

    C#中foreach原理以及模拟的实现

    这篇文章主要介绍了C#中foreach原理以及模拟的实现方法,备有详尽的注释,便于深入理解C#原理,需要的朋友可以参考下
    2014-10-10
  • C#实现洗牌算法

    C#实现洗牌算法

    洗牌算法的要求是这样的:将N个数乱序后输出.由于和扑克牌的洗牌过程比较相似所以我也就称为洗牌算法了.很多地方都不自觉的需要这个算法的支持.也可以将这个算法扩展为从N个数中取出M个不重复的数(0<M<=N).今天我们看下如何用C#来实现
    2015-03-03
  • C#中使用Join与GroupJoin将两个集合进行关联与分组

    C#中使用Join与GroupJoin将两个集合进行关联与分组

    这篇文章主要介绍了C#中使用Join与GroupJoin将两个集合进行关联与分组,文中分别对Join和GroupJoin的用法进行详细说明,需要的朋友可以参考下
    2017-12-12
  • C# using语法糖图文详解

    C# using语法糖图文详解

    这篇文章主要给大家介绍了关于C# using语法糖的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • C#基于Linq和反射实现数据持久化框架Xml4DB详解

    C#基于Linq和反射实现数据持久化框架Xml4DB详解

    在本篇文章里小编给大家整理的是关于C#基于Linq和反射实现数据持久化框架Xml4DB相关知识点,有需要的朋友们学习下。
    2019-08-08
  • opencvsharp瑕疵检测的实现示例

    opencvsharp瑕疵检测的实现示例

    本文主要介绍了opencvsharp瑕疵检测的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C# StartsWith 字符串的实例方法解析

    C# StartsWith 字符串的实例方法解析

    这篇文章主要介绍了C# StartsWith 字符串的实例方法,StartsWith 方法对于需要检查字符串的前缀是否匹配特定模式的情况非常有用,你可以根据返回的布尔值,根据需要执行相应的逻辑操作,需要的朋友可以参考下
    2024-03-03
  • C#版Tesseract库的使用技巧

    C#版Tesseract库的使用技巧

    本文给大家分享C#版Tesseract库的使用技巧,在这里大家需要注意一下tesseract的识别语言包要自己下载后包含到项目里面,并设置为始终复制,或者直接把这个文件包放到运行程序目录(bin\debug)下的,具体实现代码跟随小编一起学习下吧
    2021-05-05
  • C# Chart 简单使用教程

    C# Chart 简单使用教程

    Chart控件可以用来绘制波形图、柱状图、饼图、折线图等,用来进行数据表现是很不错的,现在简单说一下这个控件的使用方法,对C# Chart使用相关知识感兴趣的朋友一起看看吧
    2022-11-11

最新评论