C# 線程同步詳解
前言
當(dāng)線程池的線程阻塞時,線程池會創(chuàng)建額外的線程,而創(chuàng)建、銷毀和調(diào)度線程所需要相當(dāng)昂貴的內(nèi)存資源,另外,很多的開發(fā)人員看見自己程序的線程沒有做任何有用的事情時習(xí)慣創(chuàng)建更多的線程,為了構(gòu)建可伸縮、響應(yīng)靈敏的程序,我們在前面介紹了C#異步編程詳解
但是異步編程同樣也存在著很嚴重的問題,如果兩個不同的線程訪問相同的變量和數(shù)據(jù),按照我們異步函數(shù)的實現(xiàn)方式,不可能存在兩個線程同時訪問相同的數(shù)據(jù),這個時候我們就需要線程同步。多個線程同時訪問共享數(shù)據(jù)的時,線程同步能防止數(shù)據(jù)損壞,之所以強調(diào)同時這個概念,因為線程同步本質(zhì)就是計時問題。
異步和同步是相對的,同步就是順序執(zhí)行,執(zhí)行完一個再執(zhí)行下一個,需要等待、協(xié)調(diào)運行。異步就是彼此獨立,在等待某事件的過程中繼續(xù)做自己的事,不需要等待這一事件完成后再工作。線程就是實現(xiàn)異步的一個方式。異步是讓調(diào)用方法的主線程不需要同步等待另一線程的完成,從而可以讓主線程干其它的事情。
基元用戶模式和內(nèi)核模式構(gòu)造
基礎(chǔ)概念
基元:可以在代碼中使用的簡單的構(gòu)造
用戶模式:通過特殊的CPU指令協(xié)調(diào)線程,操作系統(tǒng)永遠檢測不到一個線程在基元用戶模式的構(gòu)造上阻塞。
內(nèi)核模式:由windows自身提供,在應(yīng)用程序的線程中調(diào)用由內(nèi)核實現(xiàn)的函數(shù)。
用戶模式構(gòu)造
易變構(gòu)造
C#編譯器、JIT編譯器和CPU都會對代碼進行優(yōu)化,它們盡量保證保留我們的意圖,但是從多線程的角度出發(fā),我們的意圖并不一定會得到保留,下面舉例說明:
static void Main(string[] args)
{
Console.WriteLine("讓worker函數(shù)運行5s后停止");
var t = new Thread(Worker);
t.Start();
Thread.Sleep(5000);
stop = true;
Console.ReadLine();
}
private static bool stop = false;
private static void Worker(object obj)
{
int x = 0;
while (!stop)
{
x++;
}
Console.WriteLine("worker函數(shù)停止x={0}",x);
}

編譯器如果檢查到stop為false,就生成代碼來進入一個無限循環(huán),并在循環(huán)中一直遞增x,所以優(yōu)化循環(huán)很快完成,但是編譯器只檢測stop一次,并不是每次都會檢測。
例子2---兩個線程同時訪問:
class test
{
private static int m_flag = 0;
private static int m_value = 0;
public static void Thread1(object obj)
{
m_value = 5;
m_flag = 1;
}
public static void Thread2(object obj)
{
if (m_flag == 1)
Console.WriteLine("m_value = {0}", m_value);
}
//多核CPU機器才會出現(xiàn)線程同步問題
public void Exec()
{
var thread1 = new Thread(Thread1);
var thread2 = new Thread(Thread2);
thread1.Start();
thread2.Start();
Console.ReadLine();
}
}
程序在執(zhí)行的時候,編譯器必須將變量m_flag和m_value從RAM讀入CPU寄存器,RAM先傳遞m_value的值0,thread1把值變?yōu)?,但是thread2并不知道thread2仍然認為值為0,這種問題一般來說發(fā)生在多核CPU的概率大一些,應(yīng)該CPU越多,多個線程同時訪問資源的幾率就越大。
關(guān)鍵字volatile,作用禁止C#編譯器、JTP編譯器和CPU執(zhí)行的一些優(yōu)化,如果做用于變量后,將不允許字段緩存到CPU的寄存器中,確保字段的讀寫都在RAM中進行。
互鎖構(gòu)造
System.Threading.Interlocked類中的每個方法都執(zhí)行一次原子的讀取以及寫入操作,調(diào)用某個Interlocked方法之前的任何變量寫入都在這個Interlocked方法調(diào)用之前執(zhí)行,而調(diào)用之后的任何變量讀取都在這個調(diào)用之后讀取。
Interlocked方法主要是對INT32變量進行靜態(tài)操作Add、Decrement、Compare、Exchange、CompareChange等方法,也接受object、Double等類型的參數(shù)。
原子操作:是指不會被線程調(diào)度機制打斷的操作;這種操作一旦開始,就一直運行到結(jié)束,中間不會有任何 context switch (切換到另一個線程)。
代碼演示:
說明:通過Interlocked的方法異步查詢幾個web服務(wù)器,并同時返回數(shù)據(jù),且結(jié)果只執(zhí)行一次。
//上報狀態(tài)類型
enum CoordinationStatus
{
Cancel,
Timeout,
AllDone
}
class AsyncCoordinator
{
//AllBegun 內(nèi)部調(diào)用JustEnded來遞減它
private int _mOpCount = 1;
//0=false,1=true
private int _mStatusReported = 0;
private Action<CoordinationStatus> _mCallback;
private Timer _mTimer;
//發(fā)起一個操作之前調(diào)用
public void AboutToBegin(int opsToAdd = 1)
{
Interlocked.Add(ref _mOpCount, opsToAdd);
}
//處理好一個操作的結(jié)果之后調(diào)用
public void JustEnded()
{
if (Interlocked.Decrement(ref _mOpCount) == 0)
{
ReportStatus(CoordinationStatus.AllDone);
}
}
//該方法必須在發(fā)起所有操作后調(diào)用
public void AllBegin(Action<CoordinationStatus> callback, int timeout = Timeout.Infinite)
{
_mCallback = callback;
if (timeout != Timeout.Infinite)
{
_mTimer = new Timer(TimeExpired, null, timeout, Timeout.Infinite);
JustEnded();
}
}
private void TimeExpired(object o)
{
ReportStatus(CoordinationStatus.Timeout);
}
public void Cancel()
{
ReportStatus(CoordinationStatus.Cancel);
}
private void ReportStatus(CoordinationStatus status)
{
//如果狀態(tài)從未報告過,就報告它,否則就忽略它,只調(diào)用一次
if (Interlocked.Exchange(ref _mStatusReported, 1) == 0)
{
_mCallback(status);
}
}
}
class MultiWebRequest
{
//輔助類 用于協(xié)調(diào)所有的異步操作
private AsyncCoordinator _mac = new AsyncCoordinator();
protected Dictionary<string,object> _mServers = new Dictionary<string, object>
{
{"http://www.baidu.com",null},{"http://www.Microsoft.com",null},{"http://www.cctv.com",null},
{"http://www.souhu.com",null},{"http://www.sina.com",null},{"http://www.tencent.com",null},
{"http://www.youku.com",null}
};
private Stopwatch sp;
public MultiWebRequest(int timeout = Timeout.Infinite)
{
sp = new Stopwatch();
sp.Start();
//通過異步方式一次性發(fā)起請求
var httpclient = new HttpClient();
foreach (var server in _mServers.Keys)
{
_mac.AboutToBegin(1);
httpclient.GetByteArrayAsync(server).ContinueWith(task => ComputeResult(server, task));
}
_mac.AllBegin(AllDone,timeout);
Console.WriteLine("");
}
private void ComputeResult(string server, Task<Byte[]> task)
{
object result;
if (task.Exception != null)
{
result = task.Exception.InnerException;
}
else
{
//線程池處理IO
result = task.Result.Length;
}
//保存返回結(jié)果的長度
_mServers[server] = result;
_mac.JustEnded();
}
public void Cancel()
{
_mac.Cancel();
}
private void AllDone(CoordinationStatus status)
{
sp.Stop();
Console.WriteLine("響應(yīng)耗時總計{0}",sp.Elapsed);
switch (status)
{
case CoordinationStatus.Cancel:
Console.WriteLine("操作取消");
break;
case CoordinationStatus.AllDone:
Console.WriteLine("操作完成,完成的結(jié)果如下");
foreach (var server in _mServers)
{
Console.WriteLine("{0}",server.Key);
object result = server.Value;
if (result is Exception)
{
Console.WriteLine("錯誤原因{0}",result.GetType().Name);
}
else
{
Console.WriteLine("返回字節(jié)數(shù)為:{0}",result);
}
}
break;
case CoordinationStatus.Timeout:
Console.WriteLine("操作超時");
break;
default:
throw new ArgumentOutOfRangeException("status", status, null);
}
}
}
非常建議大家參考一下以上代碼,我在對服務(wù)器進行訪問時,也會常常參考這個模型。
簡單的自旋鎖
class SomeResource
{
private SimpleSpinLock s1 = new SimpleSpinLock();
public void AccessResource()
{
s1.Enter();
//一次是有一個線程才能進入訪問
s1.Leave();
}
}
class SimpleSpinLock
{
private int _mResourceInUse;
public void Enter()
{
while (true)
{
if(Interlocked.Exchange(ref _mResourceInUse,1)==0)
return;
}
}
public void Leave()
{
Volatile.Write(ref _mResourceInUse,1);
}
}
這就是一個線程同步鎖的簡單實現(xiàn),這種鎖的最大問題在于,存在競爭的情況下會造成線程的“自旋”,這會浪費CPU的寶貴時間,組織CPU做更多的工作,因此,這種自旋鎖應(yīng)該用于保護那些執(zhí)行的非??斓拇a。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
C#中Abstract方法和Virtual方法的區(qū)別
這篇文章介紹了C#中Abstract方法和Virtual方法的區(qū)別,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
C#讀取數(shù)據(jù)庫返回泛型集合詳解(DataSetToList)
本篇文章主要是對C#讀取數(shù)據(jù)庫返回泛型集合(DataSetToList)進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-01-01
C#使用struct直接轉(zhuǎn)換下位機數(shù)據(jù)的示例代碼
這篇文章主要介紹了C#使用struct直接轉(zhuǎn)換下位機數(shù)據(jù)的示例代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01

