最近看并行编程书本的一些心得,简单记录下多线程和并行编程必知必会的几个概念,再次加深自己的理解。
.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://*.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的更多相关文章
-
Ticket Lock, CLH Lock, MCS Lock
如果不用OS提供的mutex,我们该如何实现互斥锁?(不考虑重入的情况) 1. naive lock 最简单的想法是,搞一个volatile类型的共享变量flag,值可以是flase(无锁)或者tru ...
-
Lock-Free 编程
文章索引 Lock-Free 编程是什么? Lock-Free 编程技术 读改写原子操作(Atomic Read-Modify-Write Operations) Compare-And-Swap 循 ...
-
Boost lockfree deque 生产者与消费者多对多线程应用
boost库中有一个boost::lockfree::queue类型的 队列,对于一般的需要队列的程序,其效率都算不错的了,下面使用一个用例来说明. 程序是一个典型的生产者与消费者的关系,都可以使用多 ...
-
Spike Notes on Lock based Concurrency Concepts
Motivation 承并发编程笔记Outline,这篇文章专注于记录学习基于锁的并发概念的过程中出现的一些知识点,为并发高层抽象做必要的准备. 尽管存在Doug Lee开山之作Concurrent ...
-
细说.NET中的多线程 (六 使用MemoryBarrier,Volatile进行同步)
上一节介绍了使用信号量进行同步,本节主要介绍一些非阻塞同步的方法.本节主要介绍MemoryBarrier,volatile,Interlocked. MemoryBarriers 本文简单的介绍一下这 ...
-
无锁数据结构(Lock-Free Data Structures)
一个星期前,我写了关于SQL Server里闩锁(Latches)和自旋锁(Spinlocks)的文章.2个同步原语(synchronization primitives)是用来保护SQL Serve ...
-
Java Lock
JVM中的另一种锁Lock的实现.与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的.在java.util.concurrent.locks包中有很多 ...
-
Metadata Lock原理8
http://www.kancloud.cn/taobaomysql/monthly/67141 MySQL· 5.7优化·Metadata Lock子系统的优化 背景 引入MDL锁的目的,最初是为了 ...
-
转:synchronized和LOCK的实现原理---深入JVM锁机制
JVM底层又是如何实现synchronized的? 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug ...
随机推荐
-
Android sqlite数据库自定义存放路径办法参考(未验证)
public class TestDB extends SQLiteOpenHelper { private static final String DATABASE_NAME = "use ...
-
ACM俱乐部 字符串
数制转换分数: 2 时间限制:1 秒 内存限制:32 兆 特殊判题: 否 提交:59 解决: 24 标签 进制转换 题目描述 求任意两个不同进制非负整数的转换(2进制-16进制),所给整数在long所 ...
-
Oracle创建存储过程、执行存储过程基本语法
>>>>>>>>>>>>>>>>>>>>>>>>> ...
-
ZOOKEEPER在CENTOS6上的再安装
作DUBBO时,肯定是需要的,,对的,,DUBBO也要熟悉一下才行啦.. URL: http://www.centoscn.com/CentosServer/test/2015/0120/4531.h ...
-
用Nodejs做一个简单的小爬虫
Nodejs将JavaScript语言带到了服务器端,作为js主力用户的前端们,因此获得了服务器端的开发能力,但除了用express搭建一个博客外,还有什么好玩的项目可以做呢?不如就做一个网络爬虫吧. ...
-
CentOS LNMP环境搭建 各版本
我们先下载系统包. 以下centos6.5 X64系统 进行演示.本环境适应Centos5.x CentOs6.x Centos7.x 32和64版本.如有错误请回复本文主要安装代码汇总 [PH ...
-
安装mysql5.6报错问题统计点
报错1(su进入mysql属组时报错): [root@dbserver ~]# su - mysql Last login: Thu Aug 31 17:20:03 CST 2017 on pts/1 ...
-
不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁. 正例: Iterator&l ...
-
spring Mvc 执行原理 及 xml注解配置说明 (六)
Spring MVC 执行原理 在 Spring Mvc 访问过程里,每个请求都首先经过 许多的过滤器,经 DispatcherServlet 处理; 一个Spring MVC工程里,可以配置多个的 ...
-
psycopg事务
1.事务的特性 原子性,要么完成,要么都不完成 2.psycopg的事务特点 在psycopg中,事务是由connection处理的,当第一次一个命令发送给数据库时(开启数据库操作游标), 一个事务就 ...