开篇语:

上班以后,烦恼少了,至少是没有什么好烦的了,只要负责好自己的工作就可以了,因此也有更多的时间去探索自己喜欢的程序。买回来的书已经看了一半,DEMO也敲了不少,昨晚终于在这里开BLOG,记录一些读书笔记。以我自己的经验来看,写笔记、测试、体会是加深理解的利器,往往起到事半功倍的效果。这几天在看任务、线程和同步的部分,就用这个练练笔,记录一些学习的心得。

一、一个小测试

本文讨论的是线程同步的技术,假定你已经理解相关概念。如果未接触过,或者理解得不多,且看下面的小例子:

  1. public class SharedState
  2. {
  3. public int State { get; set; }
  4. }
  5.  
  6. public class Job
  7. {
  8. private SharedState _state;
  9. public Job(SharedState state)
  10. {
  11. this._state = state;
  12. }
  13.  
  14. public void DoTheJob()
  15. {
  16. for (int i = ; i < ; i++)
  17. {
  18. this._state.State++;
  19. }
  20. }
  21. }

这里定义了两个类:SharedObject 用于保存线程之间的共享数据,有一个数据成员 State,Job类拥有一个 SharedObject 类型的成员,DoTheJob()方法中进行5000次循环累加成员的 State 的属性。

下面的测试方法中,我们会新建20个任务,执行DoTheJob()方法:

  1. public static void MultiThreadTest()
  2. {
  3. var size = ;
  4. var tasks = new Task[size];
  5. SharedState state = new SharedState();
  6. for (int i = ; i < size; i++)
  7. {
  8. tasks[i] = Task.Factory.StartNew(() =>
  9. {
  10. new Job(state).DoTheJob();
  11. });
  12. }
  13. for (int i = ; i < size; i++)
  14. {
  15. tasks[i].Wait();
  16. }
  17. Console.WriteLine(state.State);
  18. }

按照同步执行的习惯去理解的话,你可能会认为输出的结果会是:5000*20 = 100000,实际上,以上程序执行了3次的结果分别是:

41841
58509
69589

当然,这只是我的机器上的执行结果,在你的机器上会有不同的结果。这说明了一个问题:在多线程并行执行的环境下,共享的数据有可能被其他线程修改而导致出现非预期结果。

二、C#用于多个线程同步的技术

如果需要在线程中共享数据,就需要使用同步技术,C#可以用于多线程同步的技术有:

  1. lock 语句
  2. Interlocked 类
  3. Monitor 类
  4. SpinLock 结构
  5. WaitHandle 类
  6. Mutex 类
  7. Semaphore 类
  8. Event 类
  9. Barrier 类
  10. ReaderWriterLockSlim 类

1、lock 语句

lock 语句只能锁定引用类型,锁定值类型只能锁定一个副本,并无意义(实际上,对值类型使用了 lock 语句,编译器会给出一个错误)

使用 lock 语句可以将类的实例成员设置为线程安全的,一次只有一个线程能访问相同实例的相同成员。结合几个例子理解这句话:

  1. /*
  2. * 将 DoTheJob() 方法进行以下改造是否达到我们的目的了?
  3. * 答案是否定的
  4. * 改造后继续测试依然没有输出我们期待的 100000
  5. * 这里的 lock 只对使用相同实例的线程起作用
  6. * tasks[] 中每个任务都调用不同的实例,所以它们都能同时调用 DoTheJob()方法
  7. */
  8.  
  9. public void DoTheJob()
  10. {
  11. lock (this)
  12. {
  13. for (int i = ; i < ; i++)
  14. {
  15. this._state.State++;
  16. }
  17. }
  18. }

即使上面的改造并不成功,本着对比加深理解的目的,这里提一提 lock(this) 和 lock(obj) 的区别,以下的改造和以上的改造有何不同?

  1. private object syncObj = new object();
  2. public void DoTheJob()
  3. {
  4. lock (syncObj)
  5. {
  6. for (int i = ; i < ; i++)
  7. {
  8. this._state.State++;
  9. }
  10. }
  11. }

从功能上看,lock(this) 锁定了整个实例,导致锁定期间 Job 实例的成员都不能被其他访问,而不仅仅是 DoTheJob() 不能被其他线程访问。而 lock(syncObj)只会导致 DoTheJob() 不能被其他线程访问,但实例的其他成员依然可以被访问。以下的例子可以更清楚的说明这一点。

lock(this)

  1. public class LockThis
  2. {
  3. private bool _deadLock = true;
  4. public void DeadLocked()
  5. {
  6. lock (this)
  7. {
  8. while (_deadLock)
  9. {
  10. Console.WriteLine("OMG! I am locked!");
  11. Thread.Sleep();
  12. }
  13. Console.WriteLine("DeadLocked() End.");
  14. }
  15. }
  16.  
  17. public void DontLockMe()
  18. {
  19. _deadLock = false;
  20. }
  21. }
  22.  
  23. /*
  24. * lockThis 实例企图在死锁 DeadLocked() 发生5秒后
  25. * 通过 DontLockMe() 接触死锁
  26. * 但并不成功!
  27. * 因为死锁中 lock(this) 锁定了整个实例
  28. * 导致外层也有可能用同步方式访问该实例时,连非同步方法 DontLockMe() 也不能调用
  29. */
  30.  
  31. public static void LockThisMethod()
  32. {
  33. LockThis lockThis = new LockThis();
  34. Task.Factory.StartNew(lockThis.DeadLocked);
  35. Thread.Sleep();
  36. lock (lockThis)
  37. {
  38. lockThis.DontLockMe();
  39. }
  40. }

lock(syncObj)

  1. public class LockObject
  2. {
  3. private bool _deadLock = true;
  4. private object _syncObj = new object();
  5. public void DeadLocked()
  6. {
  7. lock (_syncObj)
  8. {
  9. while (_deadLock)
  10. {
  11. Console.WriteLine("OMG! I am locked!");
  12. Thread.Sleep();
  13. }
  14. Console.WriteLine("DeadLocked() End.");
  15. }
  16. }
  17.  
  18. public void DontLockMe()
  19. {
  20. _deadLock = false;
  21. }
  22. }
  23.  
  24. /*
  25. * lockObject 实例企图在死锁 DeadLocked() 发生5秒后
  26. * 通过 DontLockMe() 接触死锁
  27. * 成功了!
  28. * 因为死锁中 lock(_syncObj) 只锁定了 DeadLocked() 方法
  29. * 即使外层也有用同步方式访问该实例时,非同步方法 DontLockMe() 也可以被调用
  30. */
  31.  
  32. public static void LockObjectMethod()
  33. {
  34. LockObject lockObject = new LockObject();
  35. Task.Factory.StartNew(lockObject.DeadLocked);
  36. Thread.Sleep();
  37. lock (lockObject)
  38. {
  39. lockObject.DontLockMe();
  40. }
  41. }

总结:因为类的对象也可以用于外部的同步访问( 上面的 lock(lockThis) 和 lock(lockObject) 就模拟了这种访问 ),而且我们不能在类自身中控制这种访问,所以应该尽量使用 lock(obj) 的方式,可以比较精确的控制需要同步的范围。

说着说着好像说远了,只顾说 lock(this) 和 lock(obj) 的区别,我们要的 100000 还没出来呢 :)俺的缺点就一般不怎么扯,一扯就扯得挺远 :)

好吧,继续。可能有很多看官早就想爆料,说这TM不简单的,一二三给个出 100000 的代码出来,其实这个俺也知道,只是这不是俺的目的。学习最怕的是知其然,而不知其所以然。我们不仅要知道正确的方式,更需要知道错误的方式,更更重要的是,需要知道它为什么正确,又为什么是错误的。

再看这种,可能真的有朋友会这么做哦~ 如果不对,不对的点又在哪呢?

  1. public class SharedState
  2. {
  3. private object syncObj = new object();
  4. private int _state;
  5. public int State
  6. {
  7. get
  8. {
  9. lock (syncObj)
  10. {
  11. return _state;
  12. }
  13. }
  14. set
  15. {
  16. lock (syncObj)
  17. {
  18. _state = value;
  19. }
  20. }
  21. }
  22. }

貌似是可以的,直接对共享状态控制同步,读和写都同步了,应该没问题了

很可惜,结果是 100000 依然没有出来 :(

误区就是:对同步的过程理解错了,读和写之间 syncObj 并没有被锁定,依然有线程可以在这个期间获得值。

夜已渐深了,看到这里很多人都会有自己的答案了。下面就列出两种正确的实现方法:

1)一种是对 SharedState 进行改造,作为一种原子操作提供递增方式,将DoTheJob()中递增的代码改为调用 IncrementState() 方法

  1. public class SharedState
  2. {
  3. private object syncObj = new object();
  4. private int _state;
  5. public int State
  6. {
  7. get { return _state; }
  8. }
  9.  
  10. public int IncrementState()
  11. {
  12. lock (syncObj)
  13. {
  14. return ++_state;
  15. }
  16. }
  17. }

2)另一种是不改动 SharedState 类,使用正确的 locker ,将 lock 语句放在合适的地方

  1. public class SharedState
  2. {
  3. public int State { get; set; }
  4. }
  5.  
  6. public class Job
  7. {
  8. private SharedState _state;
  9. public Job(SharedState state)
  10. {
  11. this._state = state;
  12. }
  13.  
  14. public void DoTheJob()
  15. {
  16. for (int i = ; i < ; i++)
  17. {
  18. lock (_state)
  19. {
  20. _state.State++;
  21. }
  22. }
  23. }
  24. }

关于 lock 语句使用暂时介绍到这里,最后需要体会的:

在一个地方使用 lock 语句并不意味着,访问对象的线程都在等待,必须对每个访问共享状态的线程,都显式的使用同步功能。

如何理解并验证这句话?把 lock(this) 无法解除死锁那段代码中去掉外层的 lock(lockThis) 运行看看就知道了 :)

虽然任务Task线程里使用了lock(this)锁定实例,但是外层主线程并无使用同步功能,因此自然可以掉到 DontLockMe() 方法成功解锁!

敲码的时间总是过得很快,要洗洗睡了,明天继续总结 Interlocked 类。

C#线程同步技术(一) lock 语句的更多相关文章

  1. 线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步

    假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增1,这样的做法线程安全吗?能保证自增变量数据同步吗?本篇体验使用lock语句块和Interlocked类型方法保证自增变量 ...

  2. C#线程同步技术(二) Interlocked 类

    接昨天谈及的线程同步问题,今天介绍一个比较简单的类,Interlocked.它提供了以线程安全的方式递增.递减.交换和读取值的方法. 它的特点是: 1.相对于其他线程同步技术,速度会快很多. 2.只能 ...

  3. iOS开发系列-线程同步技术

    概述 多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务. 多条线程同时访问同一块资源,比如操作同一个对象.统一变量.同一个文件,就会引发数据错乱和数据安全的问 ...

  4. 【WIN32进阶之路】:线程同步技术纲要

    前面博客讲了互斥量(MUTEX)和关键段(CRITICAL SECTION)的使用,想来总觉不妥,就如盲人摸象一般,窥其一脚而言象,难免以偏概全,追加一篇博客查遗补漏. win32下的线程同步技术分为 ...

  5. python笔记10-多线程之线程同步(锁lock)

    前言 关于吃火锅的场景,小伙伴并不陌生,吃火锅的时候a同学往锅里下鱼丸,b同学同时去吃掉鱼丸,有可能会导致吃到生的鱼丸. 为了避免这种情况,在下鱼丸的过程中,先锁定操作,让吃火锅的小伙伴停一会,等鱼丸 ...

  6. 转:C# 线程同步技术 Monitor 和Lock

    原文地址:http://www.cnblogs.com/lxblog/archive/2013/03/07/2947182.html 今天我们总结一下 C#线程同步 中的 Monitor 类 和 Lo ...

  7. Linux/Unix 线程同步技术之互斥量(1)

    众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...

  8. Delphi 线程同步技术(转)

    上次跟大家分享了线程的标准代码,其实在线程的使用中最重要的是线程的同步问题,如果你在使用线程后,发现你的界面经常被卡死,或者无法显示出来,显示混乱,你的使用的变量值老是不按预想的变化,结果往往出乎意料 ...

  9. 多线程 - 线程同步锁(lock、Monitor)

    1. 前言 多线程编程的时候,我们不光希望两个线程间能够实现逻辑上的先后顺序运行,还希望两个不相关的线程在访问同一个资源的时候,同时只能有一个线程对资源进行操作,否则就会出现无法预知的结果. 比如,有 ...

随机推荐

  1. 1.2(SQL学习笔记)高级数据过滤

    一.AND 通过WHERE可以进行条件过滤,但只限于单个条件. 通过AND就可以连接多个条件,AND代表了和,即AND两边的条件全部满足才会通过筛选. 这就类似编程语言中的&&. 以下 ...

  2. Web安全测试指南--认证

    认证: 5.1.1.敏感数据传输: 编号 Web_Authen_01_01 用例名称 敏感数据传输保密性测试 用例描述 测试敏感数据是否通过加密通道进行传输以防止信息泄漏. 严重级别 高 前置条件 1 ...

  3. 第七章Openwrt安装服务器环境php+uhttpd+mysql

    在前面的文章中刷openwrt.配置网络环境.挂载u盘都配置成功了之后,下面的操作就变得简单起来!!!! 1. putty连接到路由器 2. 安装php opkg install php5-fastc ...

  4. 解决Hue/hiveserver2报错:java.io.IOException: Job status not available

    sql是:select count(distinct col) from db.table; 排查过程中遇到过几个不同的报错: 1. beeline -u jdbc:hive2://0.0.0.0:1 ...

  5. 突破,Objective-C开发速学手册

    <突破,Objective-C开发速学手册> 基本信息 作者: 傅志辉 出版社:电子工业出版社 ISBN:9787121207426 上架时间:2013-7-12 出版日期:2013 年8 ...

  6. go语言基础之基础数据类型 bool类型 浮点型

    1.bool类型 示例1: package main import "fmt" func main() { var a bool a = true fmt.Println(&quo ...

  7. jquery获取一组相同标签内没有class的标签

    $("ul>li[class!='pre'][class!='nex']").each(function(i){ $(this).html(i); });

  8. ubuntu 的runlevel设定

    修改ubuntu的启动级别 runlevel ----------------------------------------------------------------------------- ...

  9. [React] Detect user activity with a custom useIdle React Hook

    If the user hasn't used your application for a few minutes, you may want to log them out of the appl ...

  10. BEA公司的weblogic是什么?有什么特点?

    转自:http://zhidao.baidu.com/link?url=J9obKwHhuh1sdLoBC3pILeaq1nz_tcpScggBNeS3D0GzAz9FI002vlS2xxJD4_z6 ...