分析.Net里线程同步机制
我 们知道并行编程模型两种:一种是基于消息式的,第二种是基于共享内存式的。 前段时间项目中遇到了第二种 使用多线程开发并行程序共享资源的问题 ,今天以实际案例出发对.net里的共享内存式的线程同步机制做个总结,由于某些类库的应用属于基础,所以本次不对基本使用做出讲解,基本使用 MSDN是最好的教程。
一、volatile关键字
基本介绍: 封装了 Thread.VolatileWrite() 和 Thread.VolatileRead()的实现 ,主要作用是强制刷新高速缓存。
使用场景: 适用于在多核多CPU的机器上 解决变量在内存和高速缓存同步不及时的问题。
案例:参考下文 二、原子操作的 案例 或者 System.Collections.Concurrent命名空间下的 ConcurrentQueue ,ConcurrentDictionary 等并发集合的实现方式。
二、原子操作(Interlock)
基本介绍: 原 子操作是 实现Spinlock,Monitor,ReadWriterLock锁的基础,其实现原理是在计算机总线上标志一个信号来表示资源已经被占用 如果其他指令进行修改则等待本次操作完成后才能进行,因为原子操作是在硬件上实现的 所以速度非常快,大约在50个时钟周期。其实原子操作也可以看做一种锁。
使用场景:性 能要求较高的场合,需要对字段进行快速的同步或者对变量进行原子形式的跟新操作(例如:int b=0; b=b+1 实际分解为多条汇编指令,在多线程情况下 多条汇编指令并行的执行可能导致错误的结果,所以要保证执行 b=b+1 生成的汇编指令是一个原子形式执行 ),例如实现一个并行队列,异步队列等。
案例:一个基于事件触发机制队列的实现
001.
/// <summary>
002.
/// 表示一个实时处理队列
003.
/// </summary>
004.
public
class
ProcessQueue<T>
005.
{
006.
#region [成员]
007.
008.
private
ConcurrentQueue<IEnumerable<T>> queue;
009.
010.
private
Action<IEnumerable<T>> PublishHandler;
011.
012.
//指定处理的线程数
013.
private
int
core = Environment.ProcessorCount;
014.
015.
//正在运行的线程数
016.
private
int
runingCore = 0;
017.
018.
public
event
Action<Exception> OnException;
019.
020.
//队列是否正在处理数据
021.
private
int
isProcessing=0;
022.
023.
//队列是否可用
024.
private
bool
enabled =
true
;
025.
026.
#endregion
027.
028.
#region 构造函数
029.
030.
public
ProcessQueue(Action<IEnumerable<T>> handler)
031.
{
032.
033.
queue =
new
ConcurrentQueue<IEnumerable<T>>();
034.
035.
PublishHandler = handler;
036.
this
.OnException += ProcessException.OnProcessException;
037.
}
038.
039.
#endregion
040.
041.
#region [方法]
042.
043.
/// <summary>
044.
/// 入队
045.
/// </summary>
046.
/// <param name="items">数据集合</param>
047.
public
void
Enqueue(IEnumerable<T> items)
048.
{
049.
if
(items !=
null
)
050.
{
051.
queue.Enqueue(items);
052.
}
053.
054.
//判断是否队列有线程正在处理
055.
if
(enabled && Interlocked.CompareExchange(
ref
isProcessing, 1, 0) == 0)
056.
{
057.
if
(!queue.IsEmpty)
058.
{
059.
ThreadPool.QueueUserWorkItem(ProcessItemLoop);
060.
}
061.
else
062.
{
063.
Interlocked.Exchange(
ref
isProcessing, 0);
064.
}
065.
}
066.
}
067.
068.
/// <summary>
069.
/// 开启队列数据处理
070.
/// </summary>
071.
public
void
Start()
072.
{
073.
Thread process_Thread =
new
Thread(PorcessItem);
074.
process_Thread.IsBackground =
true
;
075.
process_Thread.Start();
076.
}
077.
078.
/// <summary>
079.
/// 循环处理数据项
080.
/// </summary>
081.
/// <param name="state"></param>
082.
private
void
ProcessItemLoop(
object
state)
083.
{
084.
//表示一个线程递归 当处理完当前数据时 则开起线程处理队列中下一条数据 递归终止条件是队列为空时
085.
//但是可能会出现 队列有数据但是没有线程去处理的情况 所有一个监视线程监视队列中的数据是否为空,如果为空
086.
//并且没有线程去处理则开启递归线程
087.
088.
if
(!enabled && queue.IsEmpty)
089.
{
090.
Interlocked.Exchange(
ref
isProcessing, 0);
091.
return
;
092.
}
093.
094.
//处理的线程数 是否小于当前CPU核数
095.
if
(Thread.VolatileRead(
ref
runingCore) <= core * 2*)
096.
{
097.
IEnumerable<T> publishFrame;
098.
//出队以后交给线程池处理
099.
if
(queue.TryDequeue(
out
publishFrame))
100.
{
101.
Interlocked.Increment(
ref
runingCore);
102.
try
103.
{
104.
PublishHandler(publishFrame);
105.
106.
if
(enabled && !queue.IsEmpty)
107.
{
108.
ThreadPool.QueueUserWorkItem(ProcessItemLoop);
109.
}
110.
else
111.
{
112.
Interlocked.Exchange(
ref
isProcessing, 0);
113.
}
114.
115.
}
116.
catch
(Exception ex)
117.
{
118.
OnProcessException(ex);
119.
}
120.
121.
finally
122.
{
123.
Interlocked.Decrement(
ref
runingCore);
124.
}
125.
}
126.
}
127.
128.
}
129.
130.
/// <summary>
131.
///定时处理帧 线程调用函数
132.
///主要是监视入队的时候线程 没有来的及处理的情况
133.
/// </summary>
134.
private
void
PorcessItem(
object
state)
135.
{
136.
int
sleepCount=0;
137.
int
sleepTime = 1000;
138.
while
(enabled)
139.
{
140.
//如果队列为空则根据循环的次数确定睡眠的时间
141.
if
(queue.IsEmpty)
142.
{
143.
if
(sleepCount == 0)
144.
{
145.
sleepTime = 1000;
146.
}
147.
else
if
(sleepCount == 3)
148.
{
149.
sleepTime = 1000 * 3;
150.
}
151.
else
if
(sleepCount == 5)
152.
{
153.
sleepTime = 1000 * 5;
154.
}
155.
else
if
(sleepCount == 8)
156.
{
157.
sleepTime = 1000 * 8;
158.
}
159.
else
if
(sleepCount == 10)
160.
{
161.
sleepTime = 1000 * 10;
162.
}
163.
else
164.
{
165.
sleepTime = 1000 * 50;
166.
}
167.
sleepCount++;
168.
Thread.Sleep(sleepTime);
169.
}
170.
else
171.
{
172.
//判断是否队列有线程正在处理
173.
if
(enabled && Interlocked.CompareExchange(
ref
isProcessing, 1, 0) == 0)
174.
{
175.
if
(!queue.IsEmpty)
176.
{
177.
ThreadPool.QueueUserWorkItem(ProcessItemLoop);
178.
}
179.
else
180.
{
181.
Interlocked.Exchange(
ref
isProcessing, 0);
182.
}
183.
sleepCount = 0;
184.
sleepTime = 1000;
185.
}
186.
}
187.
}
188.
}
189.
190.
/// <summary>
191.
/// 停止队列
192.
/// </summary>
193.
public
void
Stop()
194.
{
195.
this
.enabled =
false
;
196.
197.
}
198.
199.
/// <summary>
200.
/// 触发异常处理事件
201.
/// </summary>
202.
/// <param name="ex">异常</param>
203.
private
void
OnProcessException(Exception ex)
204.
{
205.
var tempException = OnException;
206.
Interlocked.CompareExchange(
ref
tempException,
null
,
null
);
207.
208.
if
(tempException !=
null
)
209.
{
210.
OnException(ex);
211.
}
212.
}
213.
214.
#endregion
215.
216.
}
三、自旋锁(Spinlock)
基本介绍: 在原子操作基础上实现的锁,用户态的锁,缺点是线程一直不释放CPU时间片。操作系统进行一次线程用户态到内核态的切换大约需要500个时钟周期,可以根据这个进行参考我们的线程是进行用户等待还是转到内核的等待.。
使用场景:线程等待资源时间较短的情况下使用。
案例: 和最常用的Monitor 使用方法一样 这里就不举例了,在实际场景中应该优先选择使用Monitor,除非是线程等待资源的时间特别的短。
四、监视器(Monitor)
基本介绍: 原子操作基础上实现的锁,开始处于用户态,自旋一段时间进入内核态的等待释放CPU时间片,缺点使用不当容易造成死锁 c#实现的关键字是Lock。
使用场景: 所有需要加锁的场景都可以使用。
案例: 案例太多了,这里就不列出了。
五、读写锁(ReadWriterLock)
原理分析: 原子操作基础上实现的锁,
使用场景:适用于写的次数少,读的频率高的情况。
案例:一个线程安全的缓存实现(.net 4.0 可以使用基础类库中的 ConcurrentDictionary<K,V>) 注意:老版本ReaderWriterLock已经被淘汰,新版的是ReaderWriterLockSlim
01.
class
CacheManager<K, V>
02.
{
03.
#region [成员]
04.
05.
private
ReaderWriterLockSlim readerWriterLockSlim;
06.
07.
private
Dictionary<K, V> containter;
08.
09.
#endregion
10.
11.
#region [构造函数]
12.
13.
public
CacheManager()
14.
{
15.
this
.readerWriterLockSlim =
new
ReaderWriterLockSlim();
16.
this
.containter =
new
Dictionary<K, V>();
17.
}
18.
19.
#endregion
20.
21.
#region [方法]
22.
23.
public
void
Add(K key, V value)
24.
{
25.
readerWriterLockSlim.EnterWriteLock();
26.
27.
try
28.
{
29.
containter.Add(key, value);
30.
}
31.
32.
finally
33.
{
34.
readerWriterLockSlim.ExitWriteLock();
35.
}
36.
}
37.
38.
public
V Get(K key)
39.
{
40.
41.
bool
result =
false
;
42.
V value;
43.
44.
do
45.
{
46.
readerWriterLockSlim.EnterReadLock();
47.
48.
try
49.
{
50.
result = containter.TryGetValue(key,
out
value);
51.
}
52.
53.
finally
54.
{
55.
readerWriterLockSlim.ExitWriteLock();
56.
}
57.
58.
}
while
(!result);
59.
60.
return
value;
61.
}
62.
63.
#endregion
64.
}
.net中还有其他的线程同步机制:ManualResetEventSlim ,AutoResetEvent ,SemaphoreSlim 这里就逐个进行不介绍 具体在《CLR Via C# 》中解释的非常详细,但在具体的实际开发中我还没有使用到。
最好的线程同步机制是没有同步,这取决于良好的设计,当然有些情况下无法避免使用锁。 在性能要求不高的场合基本的lock就能满足要求,但性能要求比较苛刻的情就需求更具实际场景进行选择哪种线程同步机制。
免费培训课:http://www.jinhusns.com/Products/Curriculum/?type=xcj
源码分享:http://www.jinhusns.com/Products/Download/?type=xcj
分析.Net里线程同步机制的更多相关文章
- windows核心编程 - 线程同步机制
线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...
- 【总结】Java线程同步机制深刻阐述
原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...
- ThreadLocal和线程同步机制对比
共同点: ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题. 区别: 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量. 这时该变量是多个线程共享的,使用同 ...
- linux学习笔记之线程同步机制
一.基础知识. 1:线程同步机制:互斥量,读写锁,条件变量,自旋锁,屏障. 1,互斥量:每个进程访问被互斥量保护的资源时,都需要先对互斥量进行判断. 1)互斥量重要属性:进程共享属性,健壮属性,类型属 ...
- Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁
相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...
- Java分享笔记:创建多线程 & 线程同步机制
[1] 创建多线程的两种方式 1.1 通过继承Thread类创建多线程 1.定义Thread类的子类,重写run()方法,在run()方法体中编写子线程要执行的功能. 2.创建子线程的实例对象,相当于 ...
- Java多线程编程(4)--线程同步机制
一.锁 1.锁的概念 线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...
- Java多线程 | 02 | 线程同步机制
同步机制简介 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.Java平台提供的线程同步机制包括: 锁,volatile关键字,final关键字,static关键字,以 ...
- Java并发编程:Java中的锁和线程同步机制
锁的基础知识 锁的类型 锁从宏观上分类,只分为两种:悲观锁与乐观锁. 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新 ...
随机推荐
- InputStream与InputStreamReader的区别
InputStream是字节流,多用于读取二进制数据 InputStreamReader是字符流,多用于读取文本文件.有不同的编码方式,如utf8等.可以在构造的时候指定编码方式. 例如,两者都有一个 ...
- Redis总结笔记(一):安装和常用命令
转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/112.html?1455860824 一.redis简单介绍 redis是 ...
- JSONP浅析
DEMO : JSONP示例 为什么使用JSONP JSONP和JSON是不一样的.JSON(JavaScript Object Notation)是一种基于文本的数据交换方式,或者叫做数据描述格式. ...
- salesforce 零基础学习(二十四)解析csv格式内容
salesforce中支持对csv格式的内容批量导入,可以使用dataloader,然而有些情况下,当用户没有相关权限使用dataloader导入情况下,就的需要使用VF和apex代码来搞定. 基本想 ...
- MySQL(五) MySQL中的索引详讲
序言 之前写到MySQL对表的增删改查(查询最为重要)后,就感觉MySQL就差不多学完了,没有想继续学下去的心态了,原因可能是由于别人的影响,觉得对于MySQL来说,知道了一些复杂的查询,就够了,但是 ...
- Java并发包中CyclicBarrier的工作原理、使用示例
1. CyclicBarrier的介绍与源码分析 CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时 ...
- hibernate(十)双向关联关系的CRUD
本文链接:http://www.orlion.ml/28/ 一.保存 1. 假设一个group有多个user,一个user只属于一个group,当保存user对象到数据库中时可以 User u = n ...
- 【博客美化】08.添加"扩大/缩小浏览区域大小" 按钮
博客园美化相关文章目录: [博客美化]01.推荐和反对炫酷样式 [博客美化]02.公告栏显示个性化时间 [博客美化]03.分享按钮 [博客美化]04.自定义地址栏logo [博客美化]05.添加Git ...
- poj2513Colored Sticks(无向图的欧拉回路)
/* 题意:将两端涂有颜色的木棒连在一起,并且连接处的颜色相同! 思路:将每一个单词看成一个节点,建立节点之间的无向图!判断是否是欧拉回路或者是欧拉路 并查集判通 + 奇度节点个数等于2或者0 */ ...
- vs xamarin android 读取rest
private void Btn_Click(object sender, EventArgs e) { var u = FindViewById<EditText>(Resource.I ...