并发编程 - 线程同步(一)

打印 上一主题 下一主题

主题 876|帖子 876|积分 2628

经过前面临线程的尝试使用,我们对线程的相识又进一步加深了。今天我们继续来深入学习线程的新知识 —— 线程同步。

01、什么是线程同步

线程同步是指在多线程情况下,确保多个线程在同时使用共享资源时不会发生冲突或数据不同等问题的技术,保证线程间的正确协作。它的目标是使得多个线程在执行过程中能够按照某种顺序、安全地使用共享资源。
02、为何必要线程同步

1、避免竞争条件

不知道大家还记得在《并发编程 - 初识线程》中出现的关键字volatile和特性ThreadStatic吗?它们都是为相识决多线程共享资源问题。
在多线程中当多个线程必要同时使用共享资源时,很容易产生相互竞争资源使用权的情况,这一问题也叫竞争条件。此时就可以通过线程同步技术实现多个线程按顺序使用共享资源,从而避免竞争条件。
2、保证共享资源安全

我们举个简朴的例子,假如我的银行账户里有1000元,此时我正在用电子银行在线上操作准备向我老婆的账户里转账100元,而恰巧此时我老婆拿着我的银行卡准备取款500。
假如银行系统还是一个只有多线程,没有线程同步功能的老系统,在这一前置条件下。假如恰巧我们俩在同一瞬间点了确认操作,相信此时系统会发生什么?
有大概会是系统同时收到我们俩的请求,此时我的操作线程A,起首读取我账户余额1000,然后执行转账操作把余额减100得到900,再更新至余额中。而我老婆的操作线程B因为是和我同时的,所以在读取我账户余额的时候得到的也是1000,而不是900,此时线程B执行取款500操作把余额减500得到500,再更新至余额中。
可以发现我们俩末了更新余额,无论谁更新成功末了结果都是不正确的。这个例子就导致银行账户余额终极不正确,也就是我们说的共享资源不安全。如果使用线程同步,使得线程A、B可以按顺序执行,无论谁先执行最闭幕果都会是正确的。
下面我们再来结合代码举一个经典问题 —— torn read
先解释一下什么叫torn read,可以翻译成一次读取被撕成两半。或者说在呆板级别上,要分两个MOV指令才能读完。
详细来说就是一个long类型变量_var,当一个线程把_var赋值为0x0123456789ABCDEF,而此时另一个线程来读取_var,结果读取的值是0x0123456700000000或0x0000000089ABCDEF。这同样是因为多线程导致的共享资源不安全问题。
下面看看模拟代码实现效果:
  1. public class ThreadSync
  2. {
  3.     //共享的int64变量
  4.     public static long _var;  
  5.     public static void Run()
  6.     {
  7.         //启动写入线程
  8.         var writerThread = new Thread(WriteToSharedValue);
  9.         //启动读取线程
  10.         var readerThread = new Thread(ReadFromSharedValue);
  11.         //启动线程
  12.         writerThread.Start();
  13.         readerThread.Start();
  14.         //等待线程执行完成
  15.         writerThread.Join();
  16.         readerThread.Join();
  17.     }
  18.     //写入线程
  19.     static void WriteToSharedValue()
  20.     {
  21.         //模拟分两步写入
  22.         long high = 0x01234567;
  23.         long low = 0x89ABCDEF;
  24.         unsafe
  25.         {
  26.             //将 _var 分成高低两部分写入
  27.             //写高 32 位
  28.             _var = high << 32;
  29.             // 确保读取线程能在这里读取中间值
  30.             Thread.Sleep(0);  
  31.             //写低 32 位
  32.             _var |= low;
  33.         }
  34.         Console.WriteLine($"写: 写入值 0x{_var:X16}");
  35.     }
  36.     //读取线程
  37.     static void ReadFromSharedValue()
  38.     {
  39.         // 读取共享变量的值
  40.         Console.WriteLine($"读: 读取值 0x{_var:X16}");
  41.     }
  42. }
复制代码
纵然这个方法没有使用任何线程同步方法,这个方法也是线程安全的。因为值类型特性原因,所以传给Max的两个int值会复制到方法内部,形成自己的数据副本。此时无论有多少个线程调用Max方法,每个线程处理的都是它自己的数据,线程之间并不会相互干扰。
2、用户模式同步机制

用户模式同步机制指在用户空间内完成线程的阻塞和叫醒操作,由步伐自己管理同步对象的一种同步方式,因为不涉及与操作系统内核交换,因此开销较低,更轻量级。
实现方式有SpinLock、SpinWait、Monitor(lock)等。

3、内核模式同步机制

内核模式同步机制是指在操作系统内核空间就完成线程的挂起与恢复,由操作系统管理同步对象的一种同步方式,因为每次线程同步操作都必要操作系统到场,因此必然回涉及内核态的上下文切换,同时还是涉及到操作系统内部的数据结构和资源管理,因此内核模式同步机制往往会导致较高的开销。
实现方式有Semaphore、Mutex、AutoResetEvent等。
4、混合模式同步机制

混合模式同步机制在某些情况下会根据线程竞争的情况在用户模式和内核模式之间切换。通常,当资源访问冲突较小或线程阻塞较少时,采用用户模式同步;当资源争用较多或有较大的线程等候时,自动切换到内核模式同步。
实现方式有SemaphoreSlim、ManualResetEventSlim、CountDownEvent、Barrier、ReaderWriterLockSlim等。
:测试方法代码以及示例源码都已经上传至代码库,有爱好的可以看看。https://gitee.com/hugogoos/Planner

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表