最近看并行编程书本的一些心得,简单记录下多线程和并行编程必知必会的几个概念,再次加深自己的理解。

.NET Framework4提供了一个新的命名空间System.Collections.Concurrent用于解决常用集合在并发情况下的线程安全问题(ps:通过这个命名空间还可以访问用于并行化循环和PLINQ的自定义分区器Partitioner)。这个命名空间下的所有线程安全集合都在某种程度上使用了无锁技术。也就是说,这些集合通过使用比较并交换(Compare And Swap,CAS)指令和内存屏障(Memory Barrier),避免了使用典型的互斥的重量级的锁,虽然实际开发中对性能要求不高的业务系统中加锁可以获得最经济实惠的开发效益。

一、锁

在多线程和并发这两个主题下从来都离不开锁的身影,锁是用来做并发最简单但是代价也可能是最高的方式,在某些场景下加锁是非常经济实惠的解决方案。

锁很好用,代码也极好维护,但是必须清楚不能滥用,否则可能导致严重性能问题。因为加锁会增加系统内核态与用户态之间的切换开销以及线程调度开销。我们知道,加锁、释放锁会导致上下文切换和调度延时,等待锁的线程会被挂起直至锁释放。内核态的锁需要操作系统进行一次上下文切换,在上下文切换的时候,cpu之前缓存的指令和数据都将失效,对性能影响最大。

1、使用锁的三大基本原则

a、不使用锁

b、使用小粒度的锁,常见的锁如互斥锁(lock)、读写锁(ReaderWriterLockSlim,ReaderWriterLock)等等

c、锁住尽可能短的时间

2、同步对象

为了封装锁的逻辑,通常需要一个同步对象。比如常见的简单粗暴的同步代码里,lock(sth)的sth就是一个同步对象。

同步对象必须是引用类型(字符串通常不适合做同步对象,想想为什么),而且它通常是私有的,通常是一个instance或者static field。

为了精确的控制锁的scope和粒度,我们通常会创建一个dedicated字段,比如locker,asyncObj等;

避免使用lock(this) 或者lock(typeof(sometype))或者lock(string),这种使用方法将无法封装锁的逻辑,难以避免死锁和过度的阻塞,甚至在一个进程内还会溢出app domain边界。

3、如何减少锁?

如果你的设计为了复用而有很多共享数据,那么在多线程高并发环境下使用Lock还是LockFree的同步算法都是不可避免的。

根据经验,当我们需要访问共享的可写字段时,通常就可以通过锁来同步。

为了减少锁,我们需要减少共享数据的使用。

二、CAS

1、基本原理

CAS,简单说来就是比较并交换,大致逻辑就是如果A与B相等,那么将C赋值给A。

CAS 操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。

如果内存位置的值V与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B;否则,处理器不做任何操作。

2、内部实现伪代码

bool CAS(T* ptr, T expected, T fresh)
{
if(*ptr != expected)
return false;
*ptr = fresh;
return true;
}

3、优点

CAS是CPU指令级的操作,看上去只有一步原子操作,避免了请求操作系统来裁定锁的问题,所以一般很快。CAS操作是基于共享数据不会被修改的假设,当同步冲突出现的机会很少时,这种假设就能带来较大的性能提升。主要优点如下:

a、避免通常加锁所导致的严重性能开销,减少了内核态与用户态之间的切换开销以及线程调度开销;

b、实现更细力度的并行控制,提高系统吞吐量,有些情况下可以达到成倍的关键业务的性能提升。

4、缺点

CAS虽然有明显的优点,但天下没有免费的午餐 ,通过CAS实现的LockFree也存在很多问题,比如:
a、与硬件体系结构的内存读写模型相关,所以存在移植问题
b、实现复杂,其正确性很难被证明
    (a)、受限于CPU指令
    (b)、即使简单的数据结构也要通过复杂的算法来实现
    (c)、ABA问题
c、代码难以维护
d、存在活锁(livelock)问题

所谓活锁,简单来讲就是指事物1可以使用资源,但它让其他事物先使用资源;事物2可以使用资源,但它也让其他事物先使用资源,于是两者一直谦让,结果两者都无法使用资源。

三、MemoryBarrier

为什么需要MemoryBarrier(内存屏蔽),MSDN的解释是:

MemoryBarrier is required only on multiprocessor systems with weak memory ordering (for example, a system employing multiple Intel Itanium processors).

Synchronizes memory access as follows: The processor executing the current thread cannot reorder instructions in such a way that memory accesses prior to the call to MemoryBarrier execute after memory accesses that follow the call to MemoryBarrier.

简单来说就是多核处理器会对运行CPU指令顺序重排优化,而编译后的程序可能因为编译器优化或者计算机硬件结构比如分布式系统等诸多原因,不以编码时的顺序执行,从而引发预期外的问题。

Memory Barrier就是一种在底层保证语句按顺序执行的解决方案,调用Thread.MemoryBarrier()之后的代码中内存访问不能在这之前就完成了,也就是它可以限制指令重排和内存读写的缓存。

参考:

http://stackoverflow.com/questions/3556351/why-we-need-thread-memorybarrier

Barrier类,允许多个任务同步它们不同阶段上的并发工作。

四、并行集合

System.Collections.Concurrent命名空间下主要的线程安全并行集合有如下几种:

1、ConcurrenctQueue<T>

ConcurrenctQueue是System.Collections.Queue的并发版本。它是一个FIFO(Fisrt In,First Out,先进先出)的集合。

ConcurrenctQueue是完全无锁的,但是当CAS操作失败且面临资源争用的时候,它可能会自旋并且进行重试操作。

2、ConcurrenctStack<T>

ConcurrenctStack是System.Collections.Stack的并发版本。它是一个LIFO(Lastt In,First Out,后进先出)的集合。

ConcurrenctStack是完全无锁的,但是当CAS操作失败且面临资源争用的时候,它可能会自旋并且进行重试操作。

3、ConcurrenctBag<T>

ConcurrenctBag提供了一个无序的对象集合,而且支持对象重复,当不用考虑顺序时非常有用。

ConcurrenctBag使用了很多不同的机制,最大程度地减少了同步的需求以及同步所带来的开销。

ConcurrenctBag为每一个访问集合的线程维护了一个本地队列,而且在可能的情况下,它会以无锁的方式访问这个本地队列。

ConcurrenctBag在同一个线程添加元素(生产)和删除元素(消费)的场合下效率非常高。然而,ConcurrenctBag有时候会用到锁,因此,在生产者和消费者线程完全分开的场景下效率非常低下。

4、ConcurrenctDictionary<TKey,TValue>

ConcurrenctDictionary与经典的键值对的字典类似,提供了并发的键值访问。它是System.Collections.IDictionary实现的并发版本。

ConcurrenctDictionary对于读操作是完全无锁的,它对于需要频繁使用读取的操作进行了优化。

当很多任务或者线程在字典中添加或者修改数据的时候,ConcurrenctDictionary会使用细粒度的锁。

5、 BlockingCollection<T>

BlockingCollection与经典的阻塞队列数据结构类似,它是对一个IProducerConsumerCollection<T>实例的包装器,提供了阻塞(block)和限界(bound)的能力。

BlockingCollection能够适用于有多个任务添加和删除数据的生产者-消费者的情形。

这里再顺带提一下IProducerConsumerCollection<T>这个接口,它继承自IEnumerable<T>、ICollection和IEnumerable。忍不住要为IProducerConsumerCollection<T>、IEnumerable<T>、ICollection和IEnumerable这几个接口的抽象拍手叫好。可以说,MS对集合的设计是非常富有远见并适应变化的。

PS:关于并行集合和线程安全,很久之前我也写过总结,可以参考之前的拙文浅析线程安全容器的实现

参考:

<<C#并行编程高级教程>>

http://msdn.microsoft.com/zh-cn/library/system.collections.concurrent(v=vs.110).aspx

http://msdn.microsoft.com/zh-cn/library/dd267312(v=vs.110).aspx

http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

Lock,LockFree,MemoryBarrier,ConcurrentCollection的更多相关文章

  1. Ticket Lock, CLH Lock, MCS Lock

    如果不用OS提供的mutex,我们该如何实现互斥锁?(不考虑重入的情况) 1. naive lock 最简单的想法是,搞一个volatile类型的共享变量flag,值可以是flase(无锁)或者tru ...

  2. 【C# 线程 】内存模型 与Volatile

    CPU硬件有它自己的内存模型,不同的编程语言也有它自己的内存模型. 在 C# 的语言规范中 ECMA-334,对于Volatile关键字的描述: 15.5.4 Volatile fieldsWhen ...

  3. Lock-Free 编程

    文章索引 Lock-Free 编程是什么? Lock-Free 编程技术 读改写原子操作(Atomic Read-Modify-Write Operations) Compare-And-Swap 循 ...

  4. Boost lockfree deque 生产者与消费者多对多线程应用

    boost库中有一个boost::lockfree::queue类型的 队列,对于一般的需要队列的程序,其效率都算不错的了,下面使用一个用例来说明. 程序是一个典型的生产者与消费者的关系,都可以使用多 ...

  5. Spike Notes on Lock based Concurrency Concepts

    Motivation 承并发编程笔记Outline,这篇文章专注于记录学习基于锁的并发概念的过程中出现的一些知识点,为并发高层抽象做必要的准备. 尽管存在Doug Lee开山之作Concurrent ...

  6. 细说.NET中的多线程 (六 使用MemoryBarrier,Volatile进行同步)

    上一节介绍了使用信号量进行同步,本节主要介绍一些非阻塞同步的方法.本节主要介绍MemoryBarrier,volatile,Interlocked. MemoryBarriers 本文简单的介绍一下这 ...

  7. 无锁数据结构(Lock-Free Data Structures)

    一个星期前,我写了关于SQL Server里闩锁(Latches)和自旋锁(Spinlocks)的文章.2个同步原语(synchronization primitives)是用来保护SQL Serve ...

  8. Java Lock

    JVM中的另一种锁Lock的实现.与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的.在java.util.concurrent.locks包中有很多 ...

  9. Metadata Lock原理8

    http://www.kancloud.cn/taobaomysql/monthly/67141 MySQL· 5.7优化·Metadata Lock子系统的优化 背景 引入MDL锁的目的,最初是为了 ...

随机推荐

  1. MongoDB基本管理命令

    MongoDB是一个NoSQL数据库系统:一个数据库可以包含多个集合(Collection),每个集合对应于关系数据库中的表:而每个集合中 可以存储一组由列标识的记录,列是可以自由定义的,非常灵活,由 ...

  2. RPLIDAR使用和测试

    采购的RPLIDAR刚刚到货,先拆封,内部包括雷达主机.线缆.串口USB转接.USB线.   根据说明在此处下载驱动和SDK,雷达的数据实际是通过串口输出的,所以驱动只是针对串口USB转换芯片CP21 ...

  3. myeclipse项目编码方式彻底设置

    我们团队6月10号开始做龙泉瓯江青瓷有限公司的ERP系统,采用java语言开发,在开发时我们采用的是java的流行框架struts2,前端脚本都用jquery框架,开发IDE用的是myeclipse, ...

  4. G.Interference Signal---河南省第八届程序设计大赛(dp)

    G.Interference Signal 时间限制: 2 Sec  内存限制: 128 MB提交: 47  解决: 18[提交][状态] 题目描述 Dr.Kong’s laboratory moni ...

  5. <转>浅析长度为0的数组

    前面在看Xen的源码时,遇到了一段代码,如下所示: 注意上面最后一行的代码,这里定义了一个长度为的数组,这种用法可以吗?为什么可以使用长度为0 的数组?长度为的数组到底怎么使用?……这篇文章主要针对该 ...

  6. java的三元运算符

    1.三元运算符语法:判断表达式?表达式1:表达式2: (1)三元运算符适合于判断2个值到底使用哪一个! public static void mian(String[] args){ int sex= ...

  7. SSH项目(struts+spring+hibernate)搭建_代码简化

    在上篇讲到SSH框架的搭建后,为了有利于随时能熟练的把一个SSH的项目快速的搭建起来,我又进一步对其了解学习,对代码进行了简化,大家相互讨论学习. 为什么要简化:  如果要做一个大项目,假设项目的ac ...

  8. android 不同dpi图标大小

    LDPI (Low Density Screen,120 DPI),其图标大小为 36 x 36 px.MDPI (Medium Density Screen, 160 DPI),其图标大小为 48 ...

  9. Web大文件下载控件(down2)-示例更新-Xproer.HttpDownloader

    版权所有 2009-2016 荆门泽优软件有限公司 保留所有权利 官方网站:http://www.ncmem.com/ 产品首页:http://www.ncmem.com/webapp/down2/i ...

  10. Mini projects #5 ---- Memory

    课程全名:An Introduction to Interactive Programming in Python,来自 Rice University 授课教授:Joe Warren, Scott ...