随笔 - 353, 文章 - 1, 评论 - 5, 引用 - 0

三、并行编程 - Task同步机制。TreadLocal类、Lock、Interlocked、Synchronization、ConcurrentQueue以及Barrier等


在并行计算中,不可避免的会碰到多个任务共享变量,实例,集合。虽然task自带了两个方法:task.ContinueWith()和Task.Factory.ContinueWhenAll()来实现任务串行化,但是这些简单的方法远远不能满足我们实际的开发需要,从.net 4.0开始,类库给我们提供了很多的类来帮助我们简化并行计算中复杂的数据同步问题。

一、隔离执行:不共享数据,让每个task都有一份自己的数据拷贝。

对数据共享问题处理的方式是“分离执行”,我们通过把每个Task执行完成后的各自计算的值进行最后的汇总,也就是说多个Task之间不存在数据共享了,各自做各自的事,完全分离开来。

1、传统方式

每个Task执行时不存在数据共享了,每个Task中计算自己值,最后我们汇总每个Task的Result。我们可以通过Task中传递的state参数来进行隔离执行:

    int

Sum = 0

;
Task<int>[] tasks = new Task<int>[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = new Task<int>((obj) =>
{
var start = (int)obj;
for (int j = 0; j < 1000; j++)
{
start = start + 1;
}
return start;
}, Sum);
tasks[i].Start();
}
Task.WaitAll(tasks);
for (var i = 0; i < 10; i++)
{
Sum += tasks[i].Result;
}
Console.WriteLine("Expected value {0}, Parallel value: {1}", 10000, Sum);

2、ThreadLocal类

在.Net中提供了System.Threading.ThreadLocal来创建分离。

ThreadLocal是一种提供线程本地存储的类型,它可以给每个线程一个分离的实例,来提供每个线程单独的数据结果。上面的程序我们可以使用TreadLocal:

    int Sum = 0;
Task<int>[] tasks = new Task<int>[10];

var tl = new ThreadLocal<int>

();
for (int i = 0; i < 10; i++)
{
tasks[i] = new Task<int>((obj) =>
{
tl.Value = (int)obj;
for (int j = 0; j < 1000; j++)
{
tl.Value++;
}
returntl.Value;
}, Sum);
tasks[i].Start();
}
Task.WaitAll(tasks);
for (var i = 0; i < 10; i++)
{
Sum += tasks[i].Result;
}
Console.WriteLine("Expected value {0}, Parallel value: {1}", 10000, Sum);

但是我们要注意的一点TreadLocal是针对每个线程的,不是针对每个Task的。一个Tread中可能有多个Task。

ThreadLocal类举例:

static ThreadLocal<string> local;
static void Main()
{
//创建ThreadLocal并提供默认值
local = new ThreadLocal<string>(() => "hehe"); //修改TLS的线程
Thread th = new Thread(() =>
{
local.Value = "Mgen";
Display();
}); th.Start();
th.Join();
Display();
} //显示TLS中数据值
static void Display()
{
Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, local.Value);
}

二、同步类型:通过调整task的执行,有序的执行task

同步类型是一种用来调度Task访问临界区域的一种特殊类型。在.Net 4.0中提供了多种同步类型给我们使用,主要分为:轻量级的、重量级的和等待处理型的,在下面我们会介绍常用的同步处理类型。

常用的同步类型

首先来看看.Net 4.0中常见的几种同步类型以及处理的相关问题:

同步类型以及解决问题

  • lock关键字、Montor类、SpinLock类:有序访问临界区域
  • Interlocked类:数值类型的增加或则减少
  • Mutex类:交叉同步
  • WaitAll方法:同步多个锁定(主要是Task之间的调度)
  • 申明性的同步(如Synchronization):使类中的所有的方法同步

1、Lock锁

其实最简单同步类型的使用办法就是使用lock关键字。在使用lock关键字时,首先我们需要创建一个锁定的object,而且这个object需要所有的task都能访问,其次能我们需要将我们的临界区域包含在lock块中。我们之前例子中代码可以这样加上lock:

            int Sum = 0;
Task[] tasks = new Task[10];

var obj = new Object();

for (int i = 0; i < 10; i++)
{
tasks[i] = new Task(() =>
{
for (int j = 0; j < 1000; j++)
{
lock (obj)
{ Sum = Sum + 1; }
}
});
tasks[i].Start();
}
Task.WaitAll(tasks);
Console.WriteLine("Expected value {0}, Parallel value: {1}", 10000, Sum);

其实lock关键字是使用Monitor的一种简短的方式,lock关键字自动通过调用Monitor.Enter\Monitor.Exit方法来处理获得锁以及释放锁。

2、Interlocked 联锁

Interlocked通过使用操作系统或则硬件的一些特性提供了一些列高效的静态的同步方法。其中主要提供了这些方法:Exchange、Add、Increment、CompareExchange四种类型的多个方法的重载。我们将上面的例子中使用Interlocked:

            int Sum = 0;
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = new Task(() =>
{
for (int j = 0; j < 1000; j++)
{
Interlocked.Increment(ref Sum);
}
});
             tasks[i].Start();
}
            Task.WaitAll(tasks);
Console.WriteLine("Expected value {0}, Parallel value: {1}", 10000, Sum);

3、Mutex互斥体

Mutex也是一个同步类型,在多个线程进行访问的时候,它只向一个线程授权共享数据的独立访问。我们可以通过Mutex中的WaitOne方法来获取Mutex的所有权,但是同时我们要注意的是,我们在一个线程中多少次调用过WaitOne方法,就需要调用多少次ReleaseMutex方法来释放Mutex的占有。上面的例子我们通过Mutex这样实现:

int Sum = 0;
Task[] tasks = new Task[10];

var mutex = new

 Mutex();
for (int i = 0; i < 10; i++)
{
tasks[i] = new Task(() =>
{
for (int j = 0; j < 1000; j++)
{
bool lockAcquired =

mutex.WaitOne();

            try
{
Sum++;
}
finally
{
if (lockAcquired) mutex.ReleaseMutex();
}
}
});
tasks[i].Start();
}
Task.WaitAll(tasks);
Console.WriteLine("Expected value {0}, Parallel value: {1}", 10000, Sum);

三、申明性同步

我们可以通过使用Synchronization 特性来标识一个类,从而使一个类型的字段以及方法都实现同步化。在使用Synchronization 时,我们需要将我们的目标同步的类继承于System.ContextBoundObject类型。我们来看看之前的例子我们同步标识Synchronization 的实现:

[Synchronization]
class SumClass : ContextBoundObject
{
private int _Sum; public void Increment()
{
_Sum++;
} public int GetSum()
{
return _Sum;
}
}
class Program
{
static void Main(string[] args)
{
var sum =

new

 SumClass();
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = new Task(() =>
{
for (int j = 0; j < 1000; j++)
{
sum.Increment();
}
});
tasks[i].Start();
}
Task.WaitAll(tasks);
Console.WriteLine("Expected value {0}, Parallel value: {1}", 10000, sum.GetSum()); }
}

四、并发集合

当多个线程对某个非线程安全容器并发地进行读写操作时,这些操作将导致不可预估的后果或者会导致报错。为了解决这个问题我们可以使用lock关键字或者Monitor类来给容器上锁。但锁的引入使得我们的代码更加复杂,同时也带来了更多的同步消耗。而.NET Framework 4提供的线程安全且可拓展的并发集合能够使得我们的并行代码更加容易编写,此外,锁的使用次数的减少也减少了麻烦的死锁与竞争条件的问题。.NET Framework 4主要提供了如下几种并发集合:BlockingCollection,ConcurrentBag,ConcurrentDictionary,ConcurrentQueue,ConcurrentStack。这些集合通过使用一种叫做比较并交换(compare and swap, CAS)指令和内存屏障的技术来避免使用重量级的锁。

在.Net 4.0中提供了很多并发的集合类型来让我们处理数据同步的集合的问题,这里面包括:

1.ConcurrentQueue:提供并发安全的队列集合,以先进先出的方式进行操作;
2.ConcurrentStack:提供并发安全的堆栈集合,以先进后出的方式进行操作;
3.ConcurrentBag:提供并发安全的一种无序集合;
4.ConcurrentDictionary:提供并发安全的一种key-value类型的集合。

我们在这里只做ConcurrentQueue的一个尝试,并发队列是一种线程安全的队列集合,我们可以通过Enqueue()进行排队、TryDequeue()进行出队列操作:

for (var j = 0; j < 10; j++)
{
var queue = new

ConcurrentQueue<int>

();
var count = 0;
for (var i = 0; i < 1000; i++)
{
queue.Enqueue(i);
}
var tasks = new Task[10];
for (var i = 0; i < tasks.Length; i++)
{
tasks[i] = new Task(() =>
{
while (queue.Count > 0)
{
int item;
var isDequeue = queue.

TryDequeue

(out item);
if(isDequeue) Interlocked.Increment(ref count);
}
});
tasks[i].Start();
}
try
{
Task.WaitAll(tasks);
}
catch (AggregateException e)
{
e.Handle((ex) =>
{
Console.WriteLine("Exception Message:{0}",ex.Message);
return true;
});
}
Console.WriteLine("Dequeue items count :{0}", count);
}

五、Barrier(屏障同步)

barrier叫做屏障,就像下图中的“红色线”,如果我们的屏障设为4个task就认为已经满了的话,那么执行中先到的task必须等待后到的task,通知方式也就是barrier.SignalAndWait(),屏障中线程设置操作为new Barrier(4,(i)=>{})。SignalAndWait给我们提供了超时的重载,为了能够取消后续执行

//四个task执行
static Task[] tasks = new Task[4]; static

Barrier

 barrier = null;

  static void Main(string[] args)
{
barrier

= new Barrier(tasks.Length, (i)

 =>
{
Console.WriteLine("**********************************************************");
Console.WriteLine("\n屏障中当前阶段编号:{0}\n", i.CurrentPhaseNumber);
Console.WriteLine("**********************************************************");
}); for (int j = 0; j < tasks.Length; j++)
{
tasks[j] = Task.Factory.StartNew((obj) =>
{
var single = Convert.ToInt32(obj); LoadUser(single);
barrier.SignalAndWait(); LoadProduct(single);
barrier.SignalAndWait(); LoadOrder(single);
barrier.SignalAndWait();
}, j);
} Task.WaitAll(tasks); Console.WriteLine("指定数据库中所有数据已经加载完毕!"); Console.Read();
} static void LoadUser(int num)
{
Console.WriteLine("当前任务:{0}正在加载User部分数据!", num);
} static void LoadProduct(int num)
{
Console.WriteLine("当前任务:{0}正在加载Product部分数据!", num);
} static void LoadOrder(int num)
{
Console.WriteLine("当前任务:{0}正在加载Order部分数据!", num);
}

转载 三、并行编程 - Task同步机制。TreadLocal类、Lock、Interlocked、Synchronization、ConcurrentQueue以及Barrier等的更多相关文章

  1. 三、并行编程 - Task同步机制。TreadLocal类、Lock、Interlocked、Synchronization、ConcurrentQueue以及Barrier等

    在并行计算中,不可避免的会碰到多个任务共享变量,实例,集合.虽然task自带了两个方法:task.ContinueWith()和Task.Factory.ContinueWhenAll()来实现任务串 ...

  2. .Net并行编程之同步机制

     一:Barrier(屏障同步) 二:spinLock(自旋锁) 信号量  一:CountdownEvent 虽然通过Task.WaitAll()方法也可以达到线程同步的目的. 但是Countdown ...

  3. 二、并行编程 - Task任务

    任务,基于线程池.其使我们对并行编程变得更简单,且不用关心底层是怎么实现的.System.Threading.Tasks.Task类是Task Programming Library(TPL)中最核心 ...

  4. C#并行编程-线程同步原语

    菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...

  5. C#并行编程-Task

    菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...

  6. 【转】C#异步编程及其同步机制

    C#异步编程及其同步机制 本篇文章涵盖一下几部分内容: 1. 什么是异步编程,为什么会需要异步编程 2. .NET下的异步编程及其发展 3. .NET线程同步机制及线程间数据封送 4. 异步模式 5. ...

  7. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

  8. Java:并行编程及同步使用方法

    知道java可以使用java.util.concurrent包下的 CountDownLatch ExecutorService Future Callable 实现并行编程,并在并行线程同步时,用起 ...

  9. C#~异步编程再续~大叔所理解的并行编程(Task&Parallel)

    返回目录 并行这个概念出自.net4.5,它被封装在System.Threading.Tasks命名空间里,主要提供一些线程,异步的方法,或者说它是对之前Thread进行的二次封装,为的是让开发人员更 ...

随机推荐

  1. CentOS 7 安装 .Net Core 2.0 详细步骤

    轰轰烈烈的Core 热潮,从部署环境开始.参照了网上不少前辈的教程,也遇到不少的坑,这边做个完整的笔记. 一.构建.Net core 2的应用程web发布,因为是用来测试centos上的core 环境 ...

  2. try、catch、finally详解,你不知道的异常处理

    介绍 不管是新手还是工作几年的老油条,对try{}catch{}来说是不陌生的.他可以来帮助我们获取异常信息,在try中的代码出现错误,火灾catch代码块中被捕获到.官方也给了详细的解释:. 抛出异 ...

  3. hive 表类型

    Hive表有受控表(内部表).外部表.分区表.桶表四种.   内部表,就是一般的表,前面讲到的表都是内布标,当表定义被删除的时候,表中的数据随之一并被删除.   外部表,数据存在与否和表的定义互不约束 ...

  4. iOS SDK开发汇总

    以前也做过静态库的开发,不过都是一些简单的调用,最近在做项目的时候,发现其中还有很多问题,所以建个小项目简单记录遇到的问题以及正确的解决办法. 在项目中遇到的问题如下:xib文件获取不到, story ...

  5. js array 数组添加与删除数据

    //新建数组 var ids =new Array(); //向数组中添加数据 ids.push(1); //删除下标为i的数组元素 ids.remove(i); //自定义删除函数: Array.p ...

  6. html常用标签整理

    html文档结构 <!DOCTYPE html> <html lang="zh-CN"> #这个lang表示语言,zh-CN中文的意思,整个文档的内容以中文 ...

  7. html中用href 实现点击链接弹出文件下载对话框

    浏览器支持能够打开的格式,他都会默认直接在线打开(比如word或图片),不支持的格式,他就会弹出下载提示.最好是做成.rar格式.xlsx的文件.浏览器自带下载功能. <body> < ...

  8. SSM 开发 Tars

    目录结构 tars生成的文件当成 controller 来调用 service ,service 调用 mapper POM 注意如果 mybatis是3.4.1 spring 是4.1.14的话, ...

  9. 我的Java之旅 第二课 Eclipse使用

    1.项目引用的jar包管理 在Project Explorer中找到你要添加jar包的项目,右键项目名,点击Properties. 在弹出的窗体中,点击Resource中的JAVA Build Pat ...

  10. 导入数据到MongoDB中

    import sys import json import pymongo import datetime from pymongo import MongoClient client = Mongo ...