如果程序用到了并发技术,那就要特别留意这种情况:一段代码需要修改数据,同时其他代码需要访问同一个数据。

这种情况就需要考虑同步地访问数据。

如果下面三个条件都满足,就必须用同步来保护共享的数据。

  • 多段代码正在并发运行;

  • 这几段代码在访问(读或写)同一个数据;

  • 至少有一段代码在修改(写)数据。

一 阻塞锁

如果有多个线程需要安全地读写共享数据,这种情况可以考虑使用lock语句。

一个线程进入锁后,在锁被释放之前其他线程是无法进入的:

class MyClass
{
private readonly object _mutex = new object();
private int _value;
public void Increment()
{
lock (_mutex)
{
_value = _value + 1;
}
}
}

.NET 框 架 中 还 有 很 多 其 他 类 型 的 锁, 如 Monitor、SpinLock、ReaderWriterLockSlim。

关于lock的使用,有四条重要的准则:

  • 限制锁的作用范围:

    要尽量限制锁的作用范围。应该把 lock 语句使用的对象设为私有成员,并且永远不要暴露给非本类的方法。

    每个类型通常最多只有一个锁。如果一个类型有多个锁,可考虑通过重构把它分拆成多个独立的类型。

    lock可以锁定任何引用类型,但是建议为 lock 语句定义一个专用的成员,就像上例中那样。

  • 文档中明确锁保护的内容;

  • 锁范围内的代码尽量少:在锁定时执行的代码要尽可能得少。要特别小心阻塞调用。在锁定时不要做任何阻塞操作。

  • 在控制锁的时候绝不运行随意的代码:

    在锁定时绝不要调用随意的代码。随意的代码包括引发事件、调用虚拟方法、调用委托。

    如果一定要运行随意的代码,就在释放锁之后运行。

二 异步锁

如果多个代码块需要安全地读写共享数据,并且这些代码块可能使用 await 语句,可以考虑使用SemaphoreSlim。

class MyClass1
{
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1); private int _value; public async Task DelayAndIncrementAsync()
{
await _mutex.WaitAsync();
try
{
var oldValue = _value;
await Task.Delay(TimeSpan.FromSeconds(oldValue));
_value = oldValue + 1;
}
finally
{
_mutex.Release();
}
}
}

三 阻塞信号

如果需要从一个线程发送信号给另一个线程,可以考虑使用ManualResetEventSlim。

一个ManualResetEventSlim的对象处于这两种状态其中之一:标记的(signaled)或未标记的(unsignaled)。

每个线程都可以把事件设置为signaled 状态,也可以把它重置为 unsignaled 状态。线程也可等待事件变为 signaled 状态。

下面的两个方法被两个独立的线程调用,一个线程等待另一个线程的信号:

class MyClass2
{
private readonly ManualResetEventSlim _mres = new ManualResetEventSlim();
private int _value;
public int WaitForInitialization()
{
_mres.Wait();
return _value;
}
public void InitializeFromAnotherThread()
{
_value = 13;
_mres.Set();
}
}

在 .NET 框架中,还有一些线程同步信号类型。ManualResetEvent、AutoResetEvent、CountdownEvent。

四 异步信号

需要在代码的各个部分间发送通知,并且要求接收方必须进行异步等待。

如果该通知只需要发送一次,那可用 TaskCompletionSource<T> 异步发送。

发送代码调用TrySetResult,接收代码等待它的 Task 属性:

class MyClass3
{
private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
private int _value1;
private int _value2;
public async Task<int> WaitForInitializationAsync()
{
await _tcs.Task;
return _value1 + _value2;
}
public void Initialize()
{
_value1 = 13;
_value2 = 17;
_tcs.TrySetResult(null);
}
}

以上。

最新文章

  1. VUE 表单元素双向绑定总结
  2. Kali Linux渗透基础知识整理(四):维持访问
  3. SpringMVC下的Shiro权限框架的使用
  4. 基于webrtc的资源释放问题(一)
  5. ps通道磨皮
  6. Windows XP PRO SP3 - Full ROP calc shellcode
  7. A Script Pro nginx URL重写规则无法播放MP4解决方法
  8. QDomDocument Access violation writing location
  9. Scala-Partial Functions(偏函数)
  10. nand烧写分析/内核在启动过程中式如何将这个文件映射成/目录及各子目录的?
  11. 模拟app上商品详情点击图片放大并且可以切换大图
  12. Java并发专题 带返回结果的批量任务执行 CompletionService ExecutorService.invokeAll(转)
  13. Python LeetCode
  14. 如何配置 Health Check?- 每天5分钟玩转 Docker 容器技术(107)
  15. HDU2973(威尔逊定理)
  16. memset的用法
  17. MySQL 数据查询
  18. 持续集成 自动化构建、测试、部署您的Coding代码
  19. vue-cli项目开发/生产环境代理实现跨域请求+webpack配置开发/生产环境的接口地址
  20. C# DataTable 操作

热门文章

  1. GitLab:Your account has been blocked.
  2. 物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)
  3. 虚言妙诀终虚见,面试躬行是致知,Python技术面试策略与技巧实战记录
  4. git diff与linux diff的输出格式之unified format
  5. Eplan创建符号
  6. Dapr学习(4)之eShopOnDapr部署(Rancher2.63&amp;k3s)
  7. mybatis 10: 动态sql --- part2
  8. 听,引擎的声音「GitHub 热点速览 v.22.33」
  9. 管理 MongoDB 用户和权限
  10. idea中无法在@Test 之下使用Scanner