.Net多线程编程—同步机制
1.简介
新的轻量级同步原语:Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLock,SpinWait。轻量级同步原语只能用在一个进程内。而相应的那些重量级版本支持跨进程的同步。
2.Barrier
主要成员
1)public Barrier(int participantCount, Action<Barrier> postPhaseAction);构造 函数,participantCount:参与的线程个数(参与者的个数), postPhaseAction每个阶段后执行的操作。
2) public void SignalAndWait();发出信号,表示参与者已达到屏障并等待所有其他参与者也达到屏障。
3) public bool SignalAndWait(int millisecondsTimeout); 如果所有参与者都已在指定时间内达到屏障,则为 true;否则为 false。
4) public int ParticipantCount { get; } 获取屏障中参与者的总数。
5) public long CurrentPhaseNumber { get; internal set; }获取屏障的当前阶段编号。
6)public int ParticipantsRemaining { get; }获取屏障中尚未在当前阶段发出信号的参与者的数量。每当新阶段开始时,这个值等于ParticipantCount ,每当有参与者调用这个属性时,其减一。
注意:
1) 每当屏障(Barrier实例)接收到来自所有参与者的信号之后,屏障就会递增其阶段数,运行构造函数中指定的动作,并且解除阻塞每一个参与者。
2)Barrier使用完要调用Dispose()方法释放资源
3.CountdownEvent
主要成员:
1) public int InitialCount { get; } 获取设置事件时最初的信号数。
1) public CountdownEvent(int initialCount);
2) public bool Signal();向 CountdownEvent 注册信号,同时减小CurrentCount的值。
3) public void Reset(int count);将 System.Threading.CountdownEvent.InitialCount 属性重新设置为指定值。
注意:
一定要确保每个参与工作的线程都调用了Signal,如果有至少一个没有调用,那么任务会永久阻塞。所以一般在finally块中调用Signal是个好习惯。
4.ManualResetEvent与ManualResetEventSlim
ManualResetEvent:可实现跨进程或AppDomain的同步。
主要成员:
1)public bool Reset();将事件状态设置为非终止状态,导致线程阻止,返回值指示操作是否成功。
2)public bool Set();将事件状态设置为终止状态,允许一个或多个等待线程继续,返回值指示操作是否成功。
ManualResetEventSlim:不可应用于跨进程的同步。
主要成员:
1) public bool IsSet { get; }获取是否设置了事件。
2) public void Reset();将事件状态设置为非终止状态,从而导致线程受阻,返回值指示操作是否成功。
3)public bool Set();将事件状态设置为终止状态,允许一个或多个等待线程继续,返回值指示操作是否成功。
4)public void Wait();阻止当前线程,直到设置了当前 ManualResetEventSlim 为止。
5) public void Dispose();释放资源。
6)public ManualResetEventSlim(bool initialState, int spinCount);
5.Semaphore与SemaphoreSlim
Semaphore:可实现跨进程或AppDomain的同步,可使用WaitHandle操作递减信号量的计数。
主要成员:
1)public Semaphore(int initialCount, int maximumCount);
2)public int Release();退出信号量并返回前一个计数。
3)public virtual bool WaitOne(); 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。 如果当前实例收到信号,则为 true。 如果当前实例永远收不到信号,则 System.Threading.WaitHandle.WaitOne(System.Int32,System.Boolean)永不返回。
注意:
使用完Semaphore立即调用Dispose()方法释放资源。
SemaphoreSlim:不可实现跨进程或AppDomain的同步,不可使用WaitHandle操作递减信号量的计数。
主要成员:
1)public SemaphoreSlim(int initialCount, int maxCount);
2)public int CurrentCount { get; } 获取将允许进入 System.Threading.SemaphoreSlim 的线程的数量。
3)public int Release();退出 System.Threading.SemaphoreSlim 一次。
4)public void Wait();阻止当前线程,直至它可进入 System.Threading.SemaphoreSlim 为止。
5)public WaitHandle AvailableWaitHandle { get; }返回一个可用于在信号量上等待的 System.Threading.WaitHandle。
注意:
使用完SemaphoreSlim立即调用Dispose()方法释放资源。
6.SpinLock:自旋锁,对SpinWait的包装
主要成员:
1)public void Enter(ref bool lockTaken); 采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。
2)public void Exit(bool useMemoryBarrier);释放锁
说明:
1)不要将SpinLock声明为只读字段。
2)确保每次任务结束后都释放锁。
7.SpinWait:基于自旋的等待
主要成员:
1)public static void SpinUntil(Func<bool> condition);在指定条件得到满足之前自旋。
2)public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout);在指定条件得到满足或指定超时过期之前自旋。
说明:
1)适用情形:等待某个条件满足需要的时间很短,并且不希望发生昂贵的上下文切换。
2)内存开销非常小。其是一个结构体,不会产生不必要的内存开销。
3)如果自旋时间过长,SpinWait会让出底层线程的时间片并触发上下文切换。
8.Look:互斥锁
说明:
1)通过使用lock关键字可以获得一个对象的互斥锁。
2)使用lock,这会调用System.Threading.Monitor.Enter(object obj, ref bool lockTaken)和System.Threading.Monitor.Exit(object obj)方法。
3)不要对值类型使用Lock
4)避免锁定类的外部对象,避免跨成员或类的边界获得和释放一个锁,避免获得锁的时候调用未知代码。
9.Monitor
主要成员:
1)public static void Enter(object obj, ref bool lockTaken);获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。
2)public static void Exit(object obj);释放指定对象上的排他锁。
3)public static void TryEnter(object obj, int millisecondsTimeout, ref bool lockTaken);在指定的毫秒数中,尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
说明:
1)不要对值类型使用Monitor。
2)避免锁定类的外部对象,避免跨成员或类的边界获得和释放一个锁,避免获得锁的时候调用未知代码。
10.volatile修饰符
作用:
当共享变量被不同的线程访问和更新且没有锁和原子操作的时候,最新的值总能在共享变量中表现出来。
注意:
1)可以将这个修饰符用于类和struct的字段,但不能声明使用volatile关键字的局部变量。
2)Volatile可修饰的类型为:整型,bool,带有整型的枚举,引用类型,推到为引用类型的泛型类型,不安全上下文中的指针类型以及表示指针或者句柄的平台相关类型。
11.Interlocked:为多任务或线程共享的变量提供原子操作
主要成员:
1)public static int Increment(ref int location);以原子操作的形式递增指定变量的值并存储结果。
2)public static int Add(ref int location1, int value);对两个 32 位整数进行求和并用和替换第一个整数,上述操作作为一个原子操作完成。
3)public static float CompareExchange(ref float location1, float value, float comparand); 比较两个单精度浮点数是否相等,如果相等,则替换其中一个值。
4)public static int Decrement(ref int location);以原子操作的形式递减指定变量的值并存储结果。
注意:
最大的好处:开销低,效率高。
12 使用模式
1)Barrier
public static void BarrierTest1()
{
//构造函数的参数participantCount表示参与者的数量。
//注意:父线程也是一个参与者,所以两个任务,但是Barrier的participantCount为3
//注意:无法保证任务1和任务2完成的先后顺序。
//Barrier(int participantCount, Action<Barrier> postPhaseAction);也可使 用此方法
//当所有参与者都已到达屏障后,执行要处理的任务,即对两个任务产生的数据统一处理的过程可放在此处执行。
using (Barrier bar = new Barrier())
{
Task.Factory.StartNew(() =>
{ //具体业务 //当业务完成时,执行下面这行代码;发出信号,表明任务已完成,并等待其他参与者
bar.SignalAndWait(); }); Task.Factory.StartNew(() =>
{ //具体业务 //当业务完成时,执行下面这行代码;发出信号,表明任务已完成,并等待其他参与者
bar.SignalAndWait(); }); //保证上面两个任务都能完成才执行bar.SignalAndWait();这一句之后的代码
bar.SignalAndWait();
//当上述两个任务完成后,对两个任务产生的数据进行统一处理。 } } public static void BarrierTest2()
{
//构造函数的参数participantCount表示参与者的数量。
using (Barrier bar = new Barrier())
{
Task.Factory.StartNew(() =>
{ //具体业务 //当业务完成时,执行下面这行代码;移除一个参与者
//注意:bar.SignalAndWait();与bar.RemoveParticipant();可以混用
bar.RemoveParticipant(); }); Task.Factory.StartNew(() =>
{ //具体业务 //当业务完成时,执行下面这行代码;移除一个参与者
bar.RemoveParticipant(); }); bar.SignalAndWait();
//当上述两个任务完成后,对两个任务产生的数据进行统一处理。
}
}
2)CountdownEvent
public static void CountdownEventTest()
{
//注意初始化信号数等于并行的任务数
int initialCount = N;
using (CountdownEvent cd = new CountdownEvent(initialCount))
{
//多个并行任务,完成一个减少一个信号
for (int i = ; i < N; i++)
{
Task.Factory.StartNew(() =>
{
try
{
//真正的业务
}
finally
{
//确保不论何种情况都能减少信号量,防止死循环
cd.Signal();
}
});
} //等待上述多个任务执行完毕
cd.Wait();
}
}
3)ManualResetEvent与ManualResetEventSlim
public static void ManualResetEventTest()
{
ManualResetEvent mre = new ManualResetEvent(false);
ManualResetEvent mre1 = new ManualResetEvent(false); try
{
Task.Factory.StartNew(() =>
{
//业务
mre.Set();
}); Task.Factory.StartNew(() =>
{
mre.WaitOne(); //使用任务1的数据 mre1.Set(); }); //等待任务全部执行完
mre1.WaitOne();
}
finally
{
mre.Dispose();
mre1.Dispose();
}
}
//注意:本示例并不是一个最佳实践,目的在于演示ManualResetEventSlim
//当没有更好的协调机制时,可考虑使用本示例
public static void ManualResetEventSlimTest1()
{
ManualResetEventSlim mreslim = new ManualResetEventSlim();
ManualResetEventSlim mreslim1 = new ManualResetEventSlim();
try
{
Task.Factory.StartNew(() =>
{
mreslim.Set(); //业务 mreslim.Reset(); }); Task.Factory.StartNew(() =>
{
//当mreslim.Set()被调用时,mreslim.Wait()立即返回,即解除阻塞。
mreslim.Wait();
//直到mreslim.Reset()被调用,循环才会结束
while (mreslim.IsSet)
{
//业务
}
mreslim1.Set();
}); //等待第二个任务完成
mreslim1.Wait();
}
finally
{
mreslim.Dispose();
mreslim1.Dispose();
}
}
4)Semaphore与SemaphoreSlim
public static void SemaphoreSlimTest()
{
int initialCount = ;//可以是其他值
List<string> list = new List<string>();
var tasks = new Task[initialCount];
//如果使用Semaphore,实例化的时候,那么最少要传递两个参数,信号量的初始请求数和信号量的最大请求数
using(SemaphoreSlim ssl = new SemaphoreSlim(initialCount))
{
for (int i = ; i < initialCount; i++)
{
int j = i;
tasks[j] = Task.Factory.StartNew(() =>
{
try
{
//等待,直到进入SemaphoreSlim为止
//如果使用Semaphore,应调用WaitOne
ssl.Wait();
//直到进入SemaphoreSlim才会执行下面的代码
list.Add(""+j);//可将这部分替换成真实的业务代码
}
finally
{
ssl.Release();
}
});
}
//注意一定要在using块的最后阻塞线程,直到所有的线程均处理完任务
//如果没有等待任务全部完成的语句,会导致SemaphoreSlim资源被提前释放。
Task.WaitAll(tasks);
}
}
5)SpinLock
public static void SpinLockTest()
{
bool lockTaken = false;
SpinLock sl = new SpinLock(true);
try
{
//获得锁
//如果不能获得锁,将会等待并不断检测锁是否可用
//获得锁后,lockTaken为true,此行代码之后的部分才会开始运行
sl.Enter(ref lockTaken); //或使用含有超时机制的TryEnter方法
//sl.TryEnter(1000,ref lockTaken);
//然后抛出超时异常
//if (!lockTaken)
//{
// throw new TimeoutException("超时异常");
//} //真正的业务。。。 }
finally
{
if (lockTaken)
{
//释放锁
//SpinLock没有使用内存屏障,因此设成false
sl.Exit(false);
}
}
}
6)SpinWait
public static void SpinWaitTest()
{
bool isTrue = false;
//任务一,处理业务,成功将isTrue设置为true
Task.Factory.StartNew(() =>
{
//处理业务,返回结果result指示是否成功
bool result = ...;
if (result)
{
isTrue = true;
}
}); //可设定等待时间,如果超时,则向下执行
Task.Factory.StartNew(() => {
SpinWait.SpinUntil(()=>isTrue,);
//真正的业务
});
}
7) Look
下面两段代码是等价的。
lock (Object)
{
//do something
} //等价代码
bool lockTaken = false;
try
{
Monitor.Enter(object,lockTaken);
//do something
}
finally
{
if(lockTaken)
{
Monitor.Exit(object);
}
}
8)Interlocked
public static void InterlockedTest()
{
Task[] tasks = new Task[];
long j = ;
for(int i=;i<;i++)
{
int t = i;
tasks[t] = Task.Factory.StartNew(()=>
{
//以安全的方式递增j
Interlocked.Increment(ref j);
});
}
Task.WaitAll(tasks);
}
-----------------------------------------------------------------------------------------
转载与引用请注明出处。
时间仓促,水平有限,如有不当之处,欢迎指正。
.Net多线程编程—同步机制的更多相关文章
- Java多线程编程(同步、死锁、生产消费者问题)
Java多线程编程(同步.死锁.生产消费): 关于线程同步以及死锁问题: 线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作: 线程死锁概念:是指两个线程都在等待对方先完成,造 ...
- Java多线程的同步机制(synchronized)
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...
- Java分享笔记:创建多线程 & 线程同步机制
[1] 创建多线程的两种方式 1.1 通过继承Thread类创建多线程 1.定义Thread类的子类,重写run()方法,在run()方法体中编写子线程要执行的功能. 2.创建子线程的实例对象,相当于 ...
- python多线程编程—同步原语入门(锁Lock、信号量(Bounded)Semaphore)
摘录python核心编程 一般的,多线程代码中,总有一些特定的函数或者代码块不希望(或不应该)被多个线程同时执行(比如两个线程运行的顺序发生变化,就可能造成代码的执行轨迹或者行为不相同,或者产生不一致 ...
- 【C/C++多线程编程之十】pthread线程私有数据
多线程编程之线程私有数据 Pthread是 POSIX threads 的简称.是POSIX的线程标准. 线程同步从相互排斥量[C/C++多线程编程之六]pthread相互排 ...
- Java多线程编程(4)--线程同步机制
一.锁 1.锁的概念 线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...
- java面试必问:多线程的实现和同步机制,一文帮你搞定多线程编程
前言 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线 ...
- Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁
相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...
- .NET面试题解析(07)-多线程编程与线程同步
系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...
随机推荐
- SQL点滴21—几个有点偏的语句
原文:SQL点滴21-几个有点偏的语句 SQL语句是一种集合操作,就是批量操作,它的速度要比其他的语言快,所以在设计的时候很多的逻辑都会放在sql语句或者存储过程中来实现,这个是一种设计思想.但是今天 ...
- HTTP2协议之HPACK--之头部压缩规范介绍
接下来打算把HTTP2协议的头部压缩算法给翻译下,敬请等候. HPACK - Header Compression for HTTP/2 HPACK:HTTP/2头部压缩 概要说明 这个规范定义了HP ...
- Spring IOC之 使用JSR 330标准注解
从Spring 3.0开始,Spring提供了对 JSR 330标准注解的支持.这些注解可以喝Spring注解一样被扫描到.你只需要将相关的Jar包加入到你的classpath中即可. 注意:如果你使 ...
- 如何查找Linux的函数定义的位置?
网上的许多站点提供这样的服务,如下面这个: http://lxr.free-electrons.com/ident?v=3.10 Linux的错误返回值:3.10版本 Linux/include/ua ...
- [置顶] Vector ArrayList区别剖析
Java中Vector与ArrayList的区别?这是一个很常见的面试题目:) Vector与ArrayList其实是非常相似的,不信,你可以看看源码,如果说真的有什么区别的话,大概有以下三点: 1: ...
- Array(数组)的使用
方法 说明 Concat() 连接2个或多个数组,并返回结果 Push() 向数组末尾添加一个或多个元素,并返回新的长度 Reverse() 颠倒数组中元素的顺序 Sort() 对数组的元素进行排序 ...
- 实现基本的CRUD功能
文] 使用 MVC 5 的 EF6 Code First 入门 系列:实现基本的CRUD功能 2014-04-28 16:29 by Bce, 428 阅读, 0 评论, 收藏, 编辑 英文渣水平,大 ...
- IDE编程环境
Vim配置及说明——IDE编程环境 目录 Vim配置及说明——IDE编程环境 1.基本及字体 2.插件管理 3.主题风格 4.窗口设置 5.目录树导航 6.标签导航 7.taglist 8.多文档编辑 ...
- 《剑指Offer》面试题-从头到尾打印链表
题目描述: 输入一个链表,从尾到头打印链表每个节点的值. 输入: 每个输入文件仅包含一组测试样例.每一组测试案例包含多行,每行一个大于0的整数,代表一个链表的节点.第一行是链表第一个节点的值,依次类推 ...
- Java泛型学习笔记--Java泛型和C#泛型比较学习(一)
总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图.C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型 ...