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

.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. db2设置共享内存

    db2 UPDATE DBM CFG USING INSTANCE_MEMORY 5242880 IMMEDIATEdb2 UPDATE DBM CFG USING INSTANCE_MEMORY A ...

  2. iOS多线程 GCD常见用法

    Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法. dispatch queue分成以下三种: 1)运行在主线程的Main queue,通过dispat ...

  3. UITabar 设置字体大小/颜色

    1.设置被选中字体颜色 [self.tabBarController.tabBar setTintColor:[UIColor blackColor]];

  4. TCP/IP入门(3) --传输层

    原文:http://blog.csdn.net/zjf280441589/article/category/1854365 传输层的主要功能 1)传输层为应用进程之间提供端到端的逻辑通信(网络层是为主 ...

  5. Caused by: java.lang.UnsatisfiedLinkError: Couldn't load BaiduMapVOS_v2_1_3: findLibrary returned nu

    我是在整合百度LBS服务与百度语音识别服务的时候遇到这个问题的........ 解决办法是:不要导armeabi-v7a这个文件夹即可. 貌似还有的人试一下以下这种方法(这种方法来自:http://w ...

  6. Spring 常用的一些工具类

    学习Java的人,或者开发很多项目,都需要使用到Spring 这个框架,这个框架对于java程序员来说.学好spring 就不怕找不到工作.我们时常会写一些工具类,但是有些时候 我们不清楚,我们些的工 ...

  7. 站点图标favicon.ico

    favicon.ico图标: 网站的favicon.ico需要一次额外的http请求,无论你是否有在html里面添加 link链接 <link rel="shortcut icon&q ...

  8. mysql 索引问题

    转自http://www.blogjava.net/happyenjoylife/archive/2011/12/17/366639.html mysql innodb存储与索引的总结 Innodb存 ...

  9. vim中输入tab符

    今天在写Makefile时各种出错.后来发现是all:的下一行,make前面必须是tab符,不能是空格. 但是vim中只要按tab就会自动转换成4个空格.平时编程需要,也不便把这个设置取消. 查了下, ...

  10. hdu 5718(Oracle)大数加法

    曾经有一位国王,统治着一片未名之地.他膝下有三个女儿. 三个女儿中最年轻漂亮的当属Psyche.她的父亲不确定她未来的命运,于是他来到Delphi神庙求神谕. 神谕可以看作一个不含前导零的正整数n n ...