带着问题去思考! 大家好.
今天我们来了解下什么是线程同步?
首先我们先知道这些概念和一些类;
执行基本的原子性
Mutex 类
SemaphoreSlim 类
AutoResetEvent 类
ManualRestEventSlim 类
CountDownEvent 类
Barrier 类
ReaderWriterLockSilm 类
SpinWait 类
我们都知道确保当一个线程使用某些资源的时候, 同时其他线程无法使用该资源. 这引入一个概念是共享资源.
多个线程同时使用共享对象会造成很多问题. 同步线程使得对共享对象的操作能够以正确的顺序执行是非常重要的.
首先通过一个加减例子了解下 lock 处理
- public abstract class CounterBase
- {
- public abstract void Increment();
- public abstract void Decrement();
- }
- public class Counter:CounterBase
- {
- public int Count { get;private set; }
- public override void Decrement()
- {
- Count++;
- }
- public override void Increment()
- {
- Count--;
- }
- }
- public class CounterLock : CounterBase
- {
- private readonly object _synclock = new object();
- public int Count { get; private set; }
- public override void Decrement()
- {
- lock(_synclock)
- {
- Count++;
- }
- }
- public override void Increment()
- {
- lock (_synclock)
- {
- Count--;
- }
- }
- }
- static void Main(string[] args)
- {
- #region 多线程锁
- Console.WriteLine("Incorrect counter");
- var c = new Counter();
- var t1 = new Thread(() => TestCount(c));
- var t2= new Thread(() => TestCount(c));
- var t3 = new Thread(() => TestCount(c));
- t1.Start();
- t2.Start();
- t3.Start();
- t1.Join();
- t2.Join();
- t3.Join();
- Console.WriteLine("Total count:{0}",c.Count);
- Console.WriteLine("-----------");
- Console.WriteLine("Correct counter");
- var c1 = new CounterLock();
- t1 = new Thread(() => TestCount(c1));
- t2 = new Thread(() => TestCount(c1));
- t3 = new Thread(() => TestCount(c1));
- t1.Start();
- t2.Start();
- t3.Start();
- t1.Join();
- t2.Join();
- t3.Join();
- Console.WriteLine("Total count:{0}", c1.Count);
- #endregion
- }
- static void TestCount(CounterBase c)
- {
- for (int i = 0; i < 1000; i++)
- {
- c.Increment();
- c.Decrement();
- }
- }
- View Code
我们知道, 最终结果应该是 0;
因为第一个线程得到 count 为 10 增加为 11, 第二个线程得到的值是 11 并增加为 12. 第一个线程得到 count 为 12, 但是递减操作发生前, 第二个线程得到的值也是 12,. 然后第一个线程将 12 递减 11 并保存 count 中, 同时第二个线程进行了同样的操作, 结果我们进行了两次递增操作但是只有一次递减操作. 这是竞争条件 (race condition)
1: 请尽量避免使用共享对象
2: 必须是共享的状态时候, 使用原子操作. 一个操作只占一个量子的时间, 一次完成. 这说明只有当前操作完成后, 其他线程才能执行其他操作, 避免死锁, 和使用锁
3: 使用不同方式来协调线程
将等待的线程置于阻塞状态. 当线程阻塞状态, 只会占用少量 CPU 时间, 意味将引入至少一次所谓的上下文切换 (context switch 上下文切换是操作系统的线程调度器). 该调度器会保存等待的线程等待, 并切换到另一个线程, 依次恢复等待的线程的状态. 这需要消耗很多资源, 但是如果线程要被挂起很长时间的话, 这是值得的 --- 内核模式 (kernel-mode)
线程只需要等待一小段时间, 不用将线程切换到阻塞状态. 虽然线程等待时会浪费 CPU 时间, 但节省了上下文切换耗费的 CPU 时间 ---- 用户模式 (user-mode)
先尝试使用用户模式等待, 如果线程等待足够长时间. 则会切换到阻塞状态节省 CPU 资源 --- 混合模式 (hybrid)
执行基本的原子操作
不用阻塞线程就可避免竞争条件
- public class CounterLock : CounterBase
- {
- public int Count { get; private set; }
- public int _count;
- public override void Decrement()
- {
- Interlocked.Decrement(ref _count);
- }
- public override void Increment()
- {
- Interlocked.Increment(ref _count);
- }
- }
我们修改 CounterLock, 再来看看结果
我们可能会得到 0, 但是最终会得到一些不确定的非 0. 第一个例子是线程不安全的. 第二个例子中, 我们借助 Interlocked 类, 无需锁定任何对象即可获取正确结果. Interlocked 提供了 Increment.Decrement 和 Add 基于数学操作的原子方法, 编写 Counter 类时无需使用锁,
Mutex 类
- const string MutexName = "CSharpThreadingCookbook";
- static void Main(string[] args)
- {
- using (var m=new Mutex(false,MutexName))
- {
- if(!m.WaitOne(TimeSpan.FromSeconds(5),false))
- {
- Console.WriteLine("Second instance is running!");
- }
- else
- {
- Console.WriteLine("Running!");
- Console.ReadLine();
- m.ReleaseMutex();
- }
- }
- }
程序启动, 定义一个指定名称的互斥量, 设置 initialOwner 标志为 false, 这意味着如果互斥量已经被创建, 则允许程序获取该互斥量. 如果没有获得互斥量, 程序则简单显示 Running
在运行同样一个程序, 则会在 5 秒种内尝试获得互斥量, 如果此时在第一个程序中按下任意键, 第二个程序则会开始执行. 然而如果保持等待 5 秒, 第二个程序无法获得该互斥量
来源: https://www.cnblogs.com/ccaa/p/12616966.html