8天玩转并行开发——第五天 同步机制(下)

   

     承接上一篇,我们继续说下.net4.0中的同步机制,是的,当出现了并行计算的时候,轻量级别的同步机制应运而生,在信号量这一块

出现了一系列的轻量级,今天继续介绍下面的3个信号量 CountdownEvent,SemaphoreSlim,ManualResetEventSlim。

 

一: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给我们提供了

可以动态修改的解决方案。

using System.Collections.Concurrent;
using System.Threading.Tasks;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

class Program
{
//默认的容纳大小为“硬件线程“数
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();
}
}
}

我们看到有两个主要方法:Wait和Signal。每调用一次Signal相当于麻将桌上走了一个人,直到所有人都搓过麻将wait才给放行,这里同样要

注意也就是“超时“问题的存在性,尤其是在并行计算中,轻量级别给我们提供了”取消标记“的机制,这是在重量级别中不存在的,比如下面的

重载public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken),具体使用可以看前一篇文章的介绍。

 

二:SemaphoreSlim

     在.net 4.0之前,framework中有一个重量级的Semaphore,人家可以跨进程同步,咋轻量级不行,msdn对它的解释为:限制可同时访问

某一资源或资源池的线程数。关于它的重量级demo,我的上一个系列有演示,你也可以理解为CountdownEvent是 SemaphoreSlim的功能加

强版,好了,举一个轻量级使用的例子。

using System.Collections.Concurrent;
using System.Threading.Tasks;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

class Program
{
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方法来释放它。如果迟迟等不到释放,那么任务就会进入基于内核的等待,所以说如果我们知道等待的时间比较短,采

用轻量级的版本会具有更好的性能,原理大概就这样,下面举个小例子。

using System.Collections.Concurrent;
using System.Threading.Tasks;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

class Program
{
//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();

Console.Read();
}

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

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

时间: 2024-10-29 01:43:29

8天玩转并行开发——第五天 同步机制(下)的相关文章

8天玩转并行开发——第四天 同步机制(上)

     在并行计算中,不可避免的会碰到多个任务共享变量,实例,集合.虽然task自带了两个方法:task.ContinueWith()和Task.Factory .ContinueWhenAll()来实现任务串行化,但是这些简单的方法远远不能满足我们实际的开发需要,从.net 4.0开始,类库给我们提供了很多 的类来帮助我们简化并行计算中复杂的数据同步问题.   大体上分为二种: ①   并发集合类:           这个在先前的文章中也用到了,他们的出现不再让我们过多的关注同步细节. ②

8天玩转并行开发——第八天 用VS性能向导解剖你的程序

原文:8天玩转并行开发--第八天 用VS性能向导解剖你的程序            最后一篇,我们来说说vs的"性能向导",通常我们调试程序的性能一般会使用Stopwatch,如果希望更加系统的了解程序,我们就需要 用到"性能向导",通过性能报告便于我们快速的发现并找到潜在的性能问题.   首先我们上一段需要改进的代码: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq;

8天玩转并行开发——第六天 异步编程模型

原文:8天玩转并行开发--第六天 异步编程模型             在.net里面异步编程模型由来已久,相信大家也知道Begin/End异步模式和事件异步模式,在task出现以后,这些东西都可以被task包装 起来,可能有人会问,这样做有什么好处,下面一一道来.   一: Begin/End模式 1: 委托     在执行委托方法的时候,我们常常会看到一个Invoke,同时也有一对你或许不常使用的BeginInvoke,EndInvoke方法对,当然Invoke方法 是阻塞主线程,而Begi

8天玩转并行开发——第七天 简要分析任务与线程池

原文:8天玩转并行开发--第七天 简要分析任务与线程池          其实说到上一篇,我们要说的task的知识也说的差不多了,这一篇我们开始站在理论上了解下"线程池"和"任务"之间的关系,不管是 说线程还是任务,我们都不可避免的要讨论下线程池,然而在.net 4.0以后,线程池引擎考虑了未来的扩展性,已经充分利用多核微处理器 架构,只要在可能的情况下,我们应该尽量使用task,而不是线程池.   首先看一下task的结构 从图中我们可以看出Task.Factor

8天玩转并行开发——第三天 plinq的使用

原文:8天玩转并行开发--第三天 plinq的使用        相信在.net平台下,我们都玩过linq,是的,linq让我们的程序简洁优美,简直玩的是爱不释手,但是传统的linq只是串行代码,在并行的 年代如果linq不支持并行计算那该是多么遗憾的事情啊.    当然linq有很多种方式,比如linq to sql ,xml,object 等等,如果要将linq做成并行还是很简单的,这里我就举一个比较实际一点的例子, 我们知道为了更快的响应用户操作,码农们想尽了各种办法,绞尽了脑汁,其中有一

8天玩转并行开发——第一天 Parallel的使用

      随着多核时代的到来,并行开发越来越展示出它的强大威力,像我们这样的码农再也不用过多的关注底层线程的实现和手工控制, 要了解并行开发,需要先了解下两个概念:"硬件线程"和"软件线程".   1. 硬件线程     相信大家手头的电脑都是双核以上的,像我这样古董的电脑都是双核的,这样的双核叫做物理内核.   硬件线程又叫做逻辑内核,我们可以在"任务管理器"中查看"性能"标签页,如下图,我们知道有2个硬件线程.    

8天玩转并行开发——第二天 Task的使用

     在我们了解Task之前,如果我们要使用多核的功能可能就会自己来开线程,然而这种线程模型在.net 4.0之后被一种称为基于 "任务的编程模型"所冲击,因为task会比thread具有更小的性能开销,不过大家肯定会有疑惑,任务和线程到底有什么区别?   1:任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行. 2:任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小       的开销和精确的控制.  

java学习 java中接口运用演示并行开发.....暑假第五天

接口在并行开发中有着重要作用        下面演示接口的例子      模拟显卡  工作   显卡应该有显示功能 和获得厂家名称的功能 interface  VideoCard  { void Display(); void GetName(); }:   下面实现Deming显卡 ,我们通过一个类来实现 上述接口 并且在类中添加自己的特性  因为显卡的厂家不同所以 实例化的类就不同    对于生产厂商只需要满足标准接口就行了 对于谁生产 都无所谓    这就是接口的用处  我们是利用 Dem

手把手玩转win8开发系列课程(8)

这节,我们继续上节的一个议程. ①添加一个主页 现在我已经定义了一个ViewModel,我就开始整合项目.第一步就是添加主页到添加到项目中去.我明白项目为何吧页面创建为空白页面,但这个空白的页面起名字叫空白一点儿意义都没有吧,我这里需要给新页面起一个更有意义的页面.原先的空白页面,我已经吧他干掉了. 鄙人是个贱体,我更喜欢添加一些新的文件夹,使其更加的有层次.因此,我加了一个新的文件夹加做pages.在此文件夹下,我右击我添加了一个叫做list的xaml的文件. 特别提醒,如果你是xaml的一个