多个线程同时共享对象会造成很多问题。同步这些线程使得对共享对象的操作能以正确的顺序执行非常重要。
竞争问题是多线程执行没有正确同步。
多个线程之间共享对象资源,存在一个竞争,需要正确的取得资源,就需要线程同步。原子操作:一个操作只占一个量子的时间,一次就可以完成,只有当前操作完成后,其它线程才能被执行其它操作。
方式1:将线程置于阻塞状态
线程阻塞时,只会占用尽可能少的CPU时间,这样至少要一次上下文切换。如果线程要挂起很长时间,这么做时值得的。这种方式又称内核模式- Kernel mode。只有操作系统的内核才能阻止线程使用CPU时间
方式2:简单的等待
用户模式:user-mode, 线程只需等待一小段时间。该方式非常轻量,速度快,如果等待时间长,则浪费大量CPU时间。
方式3:混合模式
hybrid, 混合模式先尝试使用用户模式等待,如果线程等待了足够长的时间,则切换到阻塞状态以节省CPU资源
1. 执行基本的原子操作
不用阻塞线程就可避免竞争条件
测试验证代码如下:
class Program
{
abstract class CounterBase
{
public abstract void Increment();
public abstract void Descrement();
}
static void TestCounter(CounterBase c)
{
for (int i = 0; i < 100000; i++)
{
c.Increment();
c.Descrement();
}
}
//不使用Lock
class Counter : CounterBase
{
private int _count;
public int Count { get { return _count; } }
public override void Descrement()
{
//Thread.Sleep(1);
_count–;
}
public override void Increment()
{
_count++;
}
}
//使用原子操作
class CounterWithNoLock : CounterBase
{
private int _count;
public int Count
{
get
{
return _count;
}
}
public override void Descrement()
{
Interlocked.Decrement(ref _count);
}
public override void Increment()
{
Interlocked.Increment(ref _count);
}
}
static void Main(string[] args)
{
Console.WriteLine(“Incorrect Counter”);
var c = new Counter();
var t1 = new Thread(() => TestCounter©);
var t2 = new Thread(() => TestCounter©);
var t3 = new Thread(() => TestCounter©);
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
Console.WriteLine($“Total count:{c.Count}”);
Console.WriteLine("-----------------------");
Console.WriteLine(“Correct Counter”);
var c1 = new CounterWithNoLock();
t1 = new Thread(() => TestCounter(c1));
t2 = new Thread(() => TestCounter(c1));
t3 = new Thread(() => TestCounter(c1));
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
Console.WriteLine($“Total count:{c1.Count}”);
}
}
输出:
Incorrect Counter
Total count:-2554
-----------------------
Correct Counter
Total count:0
创建3个线程来执行TestCounter方法,第一个测试代码,线程不是安全的,存在竞争,所以计数的结果不是预期的0,有可能是0,但这个计数的结果是不确定的。第二个测试代码,使用原子锁,一定会得到预期的值0。
2.使用Mutex类
一种原始的同步方式,其只对一个线程授予对共享资源的独占访问
class Program
{
//主程序定义一个指定名称的互斥量
const string MutexName = “ThreadingCookBook”;
static void Main(string[] args)
{
//设置intialOwner标志为false,意味如果互斥量已经被删除,则允许程序获取该互斥量
//程序如果没有获取到互斥量,程序则简单的显示为Running,等按下任意键,释放该互斥量
using (var m = new Mutex(false, MutexName)) //互斥量是全局的操作系统对象,最佳方式是使用using代码块来包裹
{
if (!m.WaitOne(TimeSpan.FromSeconds(5), false))
{
Console.WriteLine(“Second instance is running!”);
}
else
{
Console.WriteLine(“Running!”);
Console.ReadLine();
m.ReleaseMutex();
}
//再运行同样程序,则会再5秒内尝试获取互斥量
//如果此时在第一个程序中按下了键第二个程序则会开始执行。
//如果保持5秒,则第二个程序无法获取该互斥量
}
Console.Read();
}
}
互斥量是全局的操作系统对象,必须确保正确关闭。最佳方式使用using代码块。
3. 使用SemaphoreSlim类
该类限制了同时访问一个资源的线程数量
class Program
{
static SemaphoreSlim _semaphore = new SemaphoreSlim(4);
static void AccessDatabase(string name,int seconds)
{
Console.WriteLine($"{name} waits to access a database");
_semaphore.Wait();
Console.WriteLine($"{name} was granted an access to a database");
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} is completed");
_semaphore.Release();
}
static void Main(string[] args)
{
for (int i = 0; i <= 6; i++)
{
string thName = "Thread " + i;
int secondsToWait = 2 + 2 * i;
var t = new Thread(() => AccessDatabase(thName, secondsToWait));
t.Start();
}
}
}
创建SemaphoreSlim 实例,构造对象时指定并发线程数量4.然后启动6个不同线程。借助信号系统显示访问数据库的并发线程为4个,所以有2个线线程需要等待,直到之前线程种的某一个完成工作, Release信号。
4.AutoResetEvent类
从一个线程向另一个线程发送通知
class Program
{
private static AutoResetEvent _workerEvent = new AutoResetEvent(false);
private static AutoResetEvent _mainEvent = new AutoResetEvent(false);
static void Process(int seconds)
{
Console.WriteLine($“Starting a long running work…”);
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine(“Work is done”);
_workerEvent.Set();
Console.WriteLine(“Waiting for a main thread to complete its work”);
_mainEvent.WaitOne();
Console.WriteLine(“Starting second operation…”);
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine(“Work is done”);
_workerEvent.Set();
}
static void Main(string[] args)
{
var t = new Thread(() => Process(10));
t.Start();
Console.WriteLine(“Waiting for another thread to complete work”);
_workerEvent.WaitOne();
Console.WriteLine(“First operation is completed!”);
Console.WriteLine(“Performing an operation on a main thread”);
Thread.Sleep(TimeSpan.FromSeconds(5));
_mainEvent.Set();
Console.WriteLine(“Now running the second operation on a second thread”);
_workerEvent.WaitOne();
Console.WriteLine(“Second operation is completed”);
}
}
5.使用ManualResetEventSlim类
在线程之间以更加灵活的方法传递信号
class Program
{
static ManualResetEventSlim _mainEvent = new ManualResetEventSlim(false);
static void TravelThroughGates(string threadName, int seconds)
{
Console.WriteLine($"{threadName} falls to sleep");
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{threadName} waits for the gates to open");
_mainEvent.Wait();
Console.WriteLine($"{threadName} enters the gates!");
}
static void Main(string[] args)
{
var t1 = new Thread(() => TravelThroughGates(“Thread 1”, 5));
var t2 = new Thread(() => TravelThroughGates(“Thread 2”, 6));
var t3 = new Thread(() => TravelThroughGates(“Thread 3”, 12));
t1.Start();
t2.Start();
t3.Start();
Thread.Sleep(TimeSpan.FromSeconds(6));
Console.WriteLine(“The gates are now open”);
_mainEvent.Set();
Thread.Sleep(TimeSpan.FromSeconds(2));
_mainEvent.Reset();
Console.WriteLine(“The gates have been closed!”);
Thread.Sleep(TimeSpan.FromSeconds(10));
Console.WriteLine(“The gates are now open for the second time”);
_mainEvent.Set();
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine(“The gates have been colsed!”);
_mainEvent.Reset();
}
}
6. 使用CountDowenEvent类
使用CountdownEvent信号类来等待直到一定数量的操作完成
class Program
{
static CountdownEvent _countdown = new CountdownEvent(2);
static void PerformOperation(string message, int seconds)
{
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine(message);
_countdown.Signal();
}
static void Main(string[] args)
{
Console.WriteLine(“Starting two operation”);
var t1 = new Thread(() => PerformOperation(“Operation 1 is completed”, 4));
var t2 = new Thread(() => PerformOperation(“Operation 2 is completed”, 8));
t1.Start();
t2.Start();
_countdown.Wait();
Console.WriteLine(“Both operations have been completed!”);
_countdown.Dispose();
}
}
7. 使用Barrier类
用于组织多个线程及时在某个时刻碰面
class Program
{
static Barrier _brrier = new Barrier(2, b => Console.WriteLine($“End of phase {b.CurrentPhaseNumber + 1}”));
static void PlayMusic(string name, string message, int seconds)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine("-----------------------------------");
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} starts to {message}");
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} finishes to {message}");
_brrier.SignalAndWait();
}
}
static void Main(string[] args)
{
var t1 = new Thread(() => PlayMusic(“The guitarist”, “Play an amazing solo”, 5));
var t2 = new Thread(() => PlayMusic(“The singer”, “Sing his song”, 2));
t1.Start();
t2.Start();
}
}
8. 使用ReaderWriterLockSlim类
创建一个线程安全的机制,在多线程中对一个集合进行读写操作。
class Program
{
static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
static Dictionary<int, int> _items = new Dictionary<int, int>();
static void Read()
{
Console.WriteLine(“Reading contents of a dictionary”);
while (true)
{
try
{
_rw.EnterReadLock();
foreach (var key in _items.Keys)
{
Thread.Sleep(TimeSpan.FromSeconds(0.1));
}
}
finally
{
_rw.ExitReadLock();
}
}
}
static void Write(string threadName)
{
while (true)
{
try
{
int newKey = new Random().Next(250);
_rw.EnterUpgradeableReadLock();
if (!_items.ContainsKey(newKey))
{
try
{
_rw.EnterWriteLock();
_items[newKey] = 1;
Console.WriteLine($“New Key {newKey} is added to a dictionary by a {threadName}”);
}
finally
{
_rw.ExitReadLock();
}
}
Thread.Sleep(TimeSpan.FromSeconds(0.1));
}
finally
{
_rw.ExitUpgradeableReadLock();
}
}
}
static void Main(string[] args)
{
new Thread(Read) { IsBackground = true }.Start();
new Thread(Read) { IsBackground = true }.Start();
new Thread(Read) { IsBackground = true }.Start();
new Thread(() => Write(“Thread 1”)) { IsBackground = true }.Start();
new Thread(() => Write(“Thread 2”)) { IsBackground = true }.Start();
Thread.Sleep(TimeSpan.FromSeconds(30));
}
}
9. 使用SpinWait类
不使用内核模型的方式来使线程等待
class Program
{
static volatile bool _isCompleted = false;
static void UserModeWait()
{
while (!_isCompleted)
{
Console.WriteLine(".");
}
Console.WriteLine();
Console.WriteLine(“Waiting is complete”);
}
static void HybridSpinWait()
{
var w = new SpinWait();
while (!_isCompleted)
{
w.SpinOnce();
Console.WriteLine(w.NextSpinWillYield);
}
Console.WriteLine(“Wating is complete”);
}
static void Main(string[] args)
{
var t1 = new Thread(UserModeWait);
var t2 = new Thread(HybridSpinWait);
Console.WriteLine(“Running user mode waiting”);
t1.Start();
Thread.Sleep(20);
_isCompleted = true;
Thread.Sleep(1000);
_isCompleted = false;
Console.WriteLine(“Running hybrid SpinWait construct waiting”);
t2.Start();
Thread.Sleep(5);
_isCompleted = true;
}
}