将学习多线程中使用共享资源的常用技术。
Mutex
semaphoreSlim
autoResetEvent
manualResetSlim
countDownEvent
Barrier
ReaderWriterLockSlim
SpinWait
Mutex 互斥
semaphoreSlim 信号灯限时。
autoResetEvent 自动重置事件
manualResetSlim 重置Slim手动
countDownEvent 倒计时事件
Barrier 障碍物
ReaderWriterLockSlim
SpinWait 旋转等待。
2.1 简介
重新设计程序来移除共享状态,从而去掉复杂的同步构造。如果必须使用共享状态,就是使用原子操作。就是只有当前操作完成后,其他线程才能执行其他操作。因此,你无须实现其他线程等待当前操作完成,这就避免了使用锁,也排除了死锁的情况。2.2 执行基本的原子操作
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication3._2
{
public class Class2_2
{
internal abstract class CounterBase
{
public abstract void Increment();
public abstract void Decrement();
}
internal static void TestCounter(CounterBase c)
{
for (int i = 0; i < 10000; i++)
{
c.Increment();
c.Decrement();
}
}
internal class Counter : CounterBase
{
private int _count;
public int Count { get { return _count; } }
public override void Decrement()
{
_count--;
}
public override void Increment()
{
_count++;
}
}
internal class CounterNoLock : CounterBase
{
private int _count;
public int Count { get { return _count; } }
public override void Decrement()
{
Interlocked.Decrement(ref _count);
}
public override void Increment()
{
Interlocked.Increment(ref _count);
}
}
}
}
static void Main(string[] args)
{
#region 2.2
Console.WriteLine("Incorret counter");
var c = new Class2_2.Counter();
var t1 = new Thread(() => Class2_2.TestCounter(c));
var t2 = new Thread(() => Class2_2.TestCounter(c));
var t3 = new Thread(()=>Class2_2.TestCounter(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 Class2_2.CounterNoLock();
t1 = new Thread(()=>Class2_2.TestCounter(c1));
t2 = new Thread(()=>Class2_2.TestCounter(c1));
t3 = new Thread(()=>Class2_2.TestCounter(c1));
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
Console.WriteLine("total count:{0}", c1.Count);
Console.ReadKey( );
#endregion
}
c.Count结果是随机的,而c1.Count借助Interlocked类,不用锁定任何对象也能获得正确结果。Interlocked提供了Increment、Decrement和add等基本数学操作的原子方法。2.3 使用Mutex类
//Main方法
#region 2.3
const string MutexName = "abcd";
using (var m = new Mutex(false, MutexName))
{
if(!m.WaitOne(5000,false))
{
Console.WriteLine("second instance is running");
Console.ReadLine();
m.ReleaseMutex();
}
else
{
Console.WriteLine("running");
Console.ReadLine();
m.ReleaseMutex();
}
}
#endregion
2.4使用SemaphoreSlim类(信号量)
static SemaphoreSlim _se = new SemaphoreSlim(4);
static void AccessDataBase(string name,int s)
{
Console.WriteLine("{0} 等待访问数据库 ",name);
_se.Wait();
Console.WriteLine("{0} 被允许访问数据库",name);
Thread.Sleep(s*1000);
Console.WriteLine("{0} 结束掉了 ",name);
_se.Release();
}
static void Main(string[] args)
{
for (int i = 0; i <=6; i++)
{
string threadName = "thread" + i;
int s = 2 + 2 * i;
var t = new Thread(() => AccessDataBase(threadName, s));
t.Start();
}
}
工作原理
- 主程序启动时,创建SemaphoreSlim的一个实例,在构造函数中指定允许的并发线程数量。main方法启动了6个不同线程,每个都尝试获取数据库的访问。但是信号量系统限制了并发量是4个线程。当有4个获取了数据库访问后,其他两个需要等待,直到之前线程完成工作调用release方法来发出信号。
- 这里我们使用了混合模式,其允许我们在等待时间很短情况下无需使用上下文切换。然后
semaphore类使用内核方式。一般没必要使用它,而在跨程序同步的场景下可以使用它。
2.5 使用AutoRestEvent类
#region 2.5
private static AutoResetEvent _w = new AutoResetEvent(false);
private static AutoResetEvent _m = new AutoResetEvent(false);
static void Process(int s)
{
Console.WriteLine("开始一个工作...");
Thread.Sleep(s*1000);
Console.WriteLine("正在工作");
_w.Set();
Console.WriteLine("等待主线程完成它的工作");
_m.WaitOne();
Console.WriteLine("开始第二个操作...");
Thread.Sleep(s*1000);
Console.WriteLine("工作完成");
_w.Set();
}
#endregion
static void Main(string[] args)
{
#region 2.5
var t = new Thread(()=>Process(10));
t.Start();
Console.WriteLine("等待其他线程完成");
_w.WaitOne();
Console.WriteLine("第一个操作完成");
Console.WriteLine("在主线程上执行操作");
_w.WaitOne();
Console.WriteLine("第二个线程完成!");
#endregion
}
2.6 使用ManualRestEventSlim类
#region 2.6
static ManualResetEventSlim _m = new ManualResetEventSlim(false);
static void TravelThroughGates(string threadName,int seconds)
{
Console.WriteLine("{0} 睡着了", threadName);
Thread.Sleep(seconds * 1000);
Console.WriteLine("{0} 等待开门!",threadName);
_m.Wait();
Console.WriteLine("{0} 进入大门!",threadName);
}
#endregion
static void Main(string[] args)
{
#region 1.
#endregion
#region 2.6
var t1 = new Thread(()=> TravelThroughGates("thread1",5));
var t2 = new Thread(()=> TravelThroughGates("thread2",6));
var t3 = new Thread(() => TravelThroughGates("thread3",12));
t1.Start();
t2.Start();
t3.Start();
Thread.Sleep(3000);
Console.WriteLine("门打开了!");
_m.Set();
Thread.Sleep(2000);
_m.Reset();
Console.WriteLine("门关闭了1!");
Thread.Sleep(10000);
Console.WriteLine("门第二次打开了!");
_m.Set();
Thread.Sleep(2000);
Console.WriteLine("门关闭了2!");
_m.Reset();
Console.ReadLine( );
#endregion
}
_m.Set时,相当于打开大门从而允许准备好的线程接收信号并继续工作。然后线程3还在睡眠状态,没有赶上时间。当调用_m.Reset()相当于关闭大门。最后一个线程已经准备好执行,但是不得不等待下一个信号,即要等待好几秒。2.7 使用CountDownEvent类
#region 2.7
static CountdownEvent _c = new CountdownEvent(2);
static void PerformOperation(string message,int s)
{
Thread.Sleep(s*1000);
Console.WriteLine(message);
_c.Signal();//向CountdownEvent 注册信号,同时减少其计数。
}
#endregion
static void Main(string[] args)
{
#region 1.
#endregion
#region 2.7
Console.WriteLine("开始两个操作");
var t1 = new Thread(()=>PerformOperation("操作1完成了",4));
var t2 = new Thread(()=>PerformOperation("操作2完成了",8));
t1.Start();
t2.Start();
_c.Wait();
Console.WriteLine("两个操作完成了!");
_c.Dispose();
Console.ReadKey();
#endregion
}
工作原理
- 当主程序启动时,创建了一个CountdownEvent实例,在其构造函数中指定了当两个操作完成时会发出信号。然后启动两个线程,当执行完成后会发出信号。一旦第二个线程完成,主线程会从等待Countdownevent的状态中返回并继续执行。针对需要等待多个异步操作完成的情形,使用本方式非常便利。然而有一个重大缺点。如果调用
_c.Signal()没有到指定的次数,那么_c.Wait()将一直等待。请确保使用countdownEvent时,所有线程完成后都要调用signal方法。
2.8使用Barrier类
*微软类库解释该作用:使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作。
c #region 2.8 static Barrier _b = new Barrier(2, b => Console.WriteLine("end of phase {0}",b.CurrentPhaseNumber+1));//获取屏障的当前阶段的编号。 static void PlayMusic(string name,string message,int s) { for (int i = 0; i < 3; i++) { Console.WriteLine("---------"); Thread.Sleep(s*1000); Console.WriteLine("{0} starts to {1}",name,message); Thread.Sleep(s*1000); Console.WriteLine("{0} finishes to {1}",name,message); _b.SignalAndWait(); } } #endregion static void Main(string[] args) { var t1 = new Thread(()=>PlayMusic("吉他手","play an amzing solo",5)); var t2 = new Thread(()=> PlayMusic("歌手 ","sing his song",2)); t1.Start(); t2.Start(); Console.Read(); } #endregion
工作原理
- 创建了Barrier类,指定了我们想要同步两个线程。在两个线程中的任何一个调用了
_b.SignalAndWait方法后,会执行一个回调函数来打印出阶段。
每个线程将向barrier发送两次信号,所以会有两个阶段。每次这两个线程调用signalAndWait方法时,barrier将执行回调函数。这在多线程迭代运算中非常有用,可以在每个迭代结束前执行一些计算。当最后一个线程调用signalandwait方法时可以在迭代结束时进行交互。
2.10 使用SpinWait类
#region 2.10
static volatile bool _isOver = false;
static void UserModeWait()
{
while (!_isOver)
{
Console.WriteLine(".");
}
Console.WriteLine();
Console.WriteLine("waiting is complete");
}
static void HybridSpinWait()
{
var w = new SpinWait();
while (!_isOver)
{
w.SpinOnce();
Console.WriteLine(w.NextSpinWillYield);
}
Console.WriteLine("waiting is complete");
}
#endregion
static void Main(string[] args)
{
#region 2.10
var t1 = new Thread(UserModeWait);
var t2 = new Thread(HybridSpinWait);
Console.WriteLine("运行用户模式等待");
t1.Start();
Thread.Sleep(20);
_isOver = true;
Thread.Sleep(1000);
_isOver = false;
Console.WriteLine("运行混合自旋等待结构等待");
t2.Start();
Thread.Sleep(5);
_isOver = true;
Console.Read();
#endregion
}
原文:https://www.cnblogs.com/anjun-xy/p/11832743.html