首页 > Python基础教程 >
-
C#教程之并发技术中同步
如果程序中用到了并发技术,一段代码需要修改数据,同时其他代码需要访问同一数据。
同步的类型:a.通信 b.数据保护。
如果以下三个条件都满足,就需要使用同步来保护数据。
- 多段代码正在并发运行;
- 这几段代码在访问(读或写)同一个数据;
- 至少有一段代码在修改数据。
1、阻塞锁 lock
多个线程需要安全的读写共享数据。
一个线程进入锁后,在锁被释放之前,其他线程是无法进入的。
锁的使用,有四条重要的规则:
- 限制锁的作用范围
- 文档中写清锁保护的内容
- 锁范围内的代码尽量少
- 在控制锁的时候,绝不运行随意的代码
首先,要尽量限制锁的作用范围。应该把 lock 语句使用的对象设为私有成员,并且永远不
要暴露给非本类的方法。每个类型通常最多只有一个锁。如果一个类型有多个锁,可考虑通
过重构把它分拆成多个独立的类型。可以锁定任何引用类型,但是我建议为 lock 语句定义
一个专用的成员,就像最后的例子那样。尤其是千万不要用 lock(this),也不要锁定 Type
或 string 类型的实例。因为这些对象是可以被其他代码访问的,这样锁定会产生死锁。
第二,要在文档中描述锁定的内容。这种做法在最初编写代码时很容易被忽略,但是在代
码变得复杂后就会变得很重要。
第三,在锁定时执行的代码要尽可能得少。要特别小心阻塞调用。在锁定时不要做任何阻
塞操作。
最后,在锁定时绝不要调用随意的代码。随意的代码包括引发事件、调用虚拟方法、调用
委托。如果一定要运行随意的代码,就在释放锁之后运行
2、异步锁 SemaphoreSlim
多个代码需要安全读写数据,并且这些代码块可能使用await语句。同步锁的规则同样适用于异步锁。
public class MyClass { /// <summary> /// 次锁保护_value /// </summary> private readonly SemaphoreSlim _mutx = new SemaphoreSlim(1); private int _value; public async Task DelayAndIncrementAsync() { await _mutx.WaitAsync(); try { await Task.Delay(TimeSpan.FromSeconds(_value)); _value = _value + 1; } finally { _mutx.Release(); } } }
3、阻塞信号 ManualResetEventSlim
需要从一个线程发送信号给另外一个线程
public class MyClass { private readonly ManualResetEventSlim _resetEvent = new ManualResetEventSlim(); private int _value; private int WaitForInitialization() { _resetEvent.Wait(); return _value; } private void InitializeFromAnotherThread() { _value = 10; _resetEvent.Set(); } }
ManualResetEventSlim 是功能强大、通用的线程间信号,但必须合理地使用
4、异步信号
需要在代码的各个部分间发送通知,并且要求接收方必须进行异步等待。
public class MyClass { private readonly TaskCompletionSource<object> _initialized = new TaskCompletionSource<object>(); private int _value1; private int _value2; public async Task<int> WaitForInitializationAsync() { await _initialized.Task; return _value1 + -_value2; } public void Initialize() { _value1 = 10; _value2 = 5; _initialized.TrySetResult(null); } }
在所有情况下都可以用 TaskCompletionSource<T> 来异步地等待:本例中,通知来自于另一
部分代码。如果只需要发送一次信号,这种方法很适合。但是如果要打开和关闭信号,这
种方法就不大合适了.。
5、限流
有一段高度并发的代码,由于它的并发程度实在太高了,需要有方法对并发性进行限流。可以避免数据项占用太多的内存。
如果发现程序的CPU或者网络连接数太多了,或者内存占用太多,就需要进行限流。
数据流和并行代码都自带了对并发性限流的方法:
IEnumerable<int> ParallelMultiplyBy2(IEnumerable<int> values) { return values.AsParallel() .WithDegreeOfParallelism(10) .Select(item => item * 2); }
并发性异步代码可以使用 SemaphoreSlim 来限流
async Task<string[]> DownloadUrlsAsync(IEnumerable<string> urls) { var httpClient = new HttpClient(); var semaphore = new SemaphoreSlim(10); var tasks = urls.Select(async url => { await semaphore.WaitAsync(); try { return await httpClient.GetStringAsync(url); } finally { semaphore.Release(); } }).ToArray(); return await Task.WhenAll(tasks); }