在并行计算中,不可避免的会碰到多个任务共享变量,实例,集合。虽然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 的实现:

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());
} [Synchronization]
class SumClass : ContextBoundObject
{
private int _Sum; public void Increment()
{
_Sum++;
} public int GetSum()
{
return _Sum;
}
}

四、并发集合

当多个线程对某个非线程安全容器并发地进行读写操作时,这些操作将导致不可预估的后果或者会导致报错。为了解决这个问题我们可以使用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等

    随笔 - 353, 文章 - 1, 评论 - 5, 引用 - 0 三.并行编程 - Task同步机制.TreadLocal类.Lock.Interlocked.Synchronization.Conc ...

  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#异步编程及其同步机制

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

  6. C#并行编程-Task

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

  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. 【模板】 ST表

    某dalao的代码 void ST(int n) { ; i <= n; i++) dp[i][] = A[i]; ; ( << j) <= n; j++) { ; i + ( ...

  2. js-ES6学习笔记-Reflect

    1.Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API.Reflect对象的设计目的有这样几个. 将Object对象的一些明显属于语言内部的方法(比如Object.d ...

  3. java中System.currentTimeMillis()

    System.curentTimeMillis();会产生一个当前的毫秒. 1.计算某个方法的耗时 long curTime = System.currentTimeMillis(); resourc ...

  4. js柯里化

    这篇文章讲的很好啊~例子很好 http://www.zhangxinxu.com/wordpress/2013/02/js-currying/ 这篇是讲函数式编程的,其中也有涉及到,说明了柯里化是一种 ...

  5. C#反射的一些经验[转载]

    写代码的时候经常需要分析已有类型的信息例如:分析现有类型自动生成类, 或者为现有的类自动增加一些功能总结了一点点经验以ClassA  a; 为例1. 通过typeof(ClassA) 或者 a.Get ...

  6. OSGI企业应用开发(十)整合Spring和Mybatis框架(三)

    上篇文章中,我们已经完成了OSGI应用中Spring和Mybatis框架的整合,本文就来介绍一下,如何在其他Bundle中,使用Mybatis框架来操作数据库. 为了方便演示,我们新建一个新的Plug ...

  7. SD从零开始57-58,第三方订单处理,跨公司销售

    [原创] SD从零开始57 第三方订单处理流程 第三方订单处理的流程Processes for Third-Party Order Processing 客户的采购订单首先在你公司的一个销售组织作为一 ...

  8. Lambda表达式 For Android

    Lambda简介 A lambda expression is a block of code with parameters. lambda表达式是带参数的代码块. 虽然看着很先进,其实Lambda ...

  9. 禅道项目管理软件 为提交Bug页面设置bug必填字段

    为提交Bug页面设置bug必填字段 by:授客 QQ:1033553122 测试环境: 禅道项目管理软件7.1.stable版本 注:仅适合windows版 步骤1.找到xampp\zentao\mo ...

  10. springcloud 入门 6 (断路器hystrix)

    hystrix:断路器 断路器是为了解决服务故障的“雪崩”,   雪崩是指,由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请 ...