C#多線程系列之線程等待_第1頁
C#多線程系列之線程等待_第2頁
C#多線程系列之線程等待_第3頁
C#多線程系列之線程等待_第4頁
C#多線程系列之線程等待_第5頁
已閱讀5頁,還剩4頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

第C#多線程系列之線程等待目錄前言volatile關(guān)鍵字三種常用等待再說自旋和阻塞SpinWait結(jié)構(gòu)屬性和方法自旋示例新的實現(xiàn)SpinLock結(jié)構(gòu)屬性和方法示例等待性能對比

前言

volatile關(guān)鍵字

volatile關(guān)鍵字指示一個字段可以由多個同時執(zhí)行的線程修改。

我們繼續(xù)使用《C#多線程(3):原子操作》中的示例:

staticvoidMain(string[]args)

for(inti=0;ii++)

newThread(AddOne).Start();

Thread.Sleep(TimeSpan.FromSeconds(5));

Console.WriteLine("sum="+sum);

Console.ReadKey();

privatestaticintsum=0;

publicstaticvoidAddOne()

for(inti=0;i100_0000;i++)

sum+=1;

}

運行后你會發(fā)現(xiàn),結(jié)果不為500_0000,而使用Interlocked.Increment(refsum);后,可以獲得準確可靠的結(jié)果。

你試試再運行下面的示例:

staticvoidMain(string[]args)

for(inti=0;ii++)

newThread(AddOne).Start();

Thread.Sleep(TimeSpan.FromSeconds(5));

Console.WriteLine("sum="+sum);

Console.ReadKey();

privatestaticvolatileintsum=0;

publicstaticvoidAddOne()

for(inti=0;i100_0000;i++)

sum+=1;

}

你以為正常了?哈哈哈,并沒有。

volatile的作用在于讀,保證了觀察的順序和寫入的順序一致,每次讀取的都是最新的一個值;不會干擾寫操作。

詳情請點擊:/zh-cn/dotnet/csharp/language-reference/keywords/volatile

其原理解釋:/2010/03/threading-understanding-the-volatile-modifier-in-csharp/

三種常用等待

這三種等待分別是:

Thread.Sleep();

Thread.SpinWait();

Task.Delay();

Thread.Sleep();會阻塞線程,使得線程交出時間片,然后處于休眠狀態(tài),直至被重新喚醒;適合用于長時間的等待;

Thread.SpinWait();使用了自旋等待,等待過程中會進行一些的運算,線程不會休眠,用于微小的時間等待;長時間等待會影響性能;

Task.Delay();用于異步中的等待,異步的文章后面才寫,這里先不理會;

這里我們還需要繼續(xù)SpinWait和SpinLock這兩個類型,最后再進行總結(jié)對照。

再說自旋和阻塞

前面我們學習過自旋和阻塞的區(qū)別,這里再來擼清楚一下。

線程等待有內(nèi)核模式(KernelMode)和用戶模式(UserModel)。

因為只有操作系統(tǒng)才能控制線程的生命周期,因此使用Thread.Sleep()等方式阻塞線程,發(fā)生上下文切換,此種等待稱為內(nèi)核模式。

用戶模式使線程等待,并不需要線程切換上下文,而是讓線程通過執(zhí)行一些無意義的運算,實現(xiàn)等待。也稱為自旋。

SpinWait結(jié)構(gòu)

微軟文檔定義:為基于自旋的等待提供支持。

SpinWait是結(jié)構(gòu)體;Thread.SpinWait()的原理就是SpinWait。

如果你想了解Thread.SpinWait()是怎么實現(xiàn)的,可以參考/233735-how-is-thread-spinwait-actually-implemented

線程阻塞是會耗費上下文切換的,對于過短的線程等待,這種切換的代價會比較昂貴的。在我們前面的示例中,大量使用了Thread.Sleep()和各種類型的等待方法,這其實是不合理的。

SpinWait則提供了更好的選擇。

屬性和方法

老規(guī)矩,先來看一下SpinWait常用的屬性和方法。

屬性:

屬性說明Count獲取已對此實例調(diào)用SpinOnce()的次數(shù)。NextSpinWillYield獲取對SpinOnce()的下一次調(diào)用是否將產(chǎn)生處理器,同時觸發(fā)強制上下文切換。

方法:

方法說明Reset()重置自旋計數(shù)器。SpinOnce()執(zhí)行單一自旋。SpinOnce(Int32)執(zhí)行單一自旋,并在達到最小旋轉(zhuǎn)計數(shù)后調(diào)用Sleep(Int32)。SpinUntil(Func)在指定條件得到滿足之前自旋。SpinUntil(Func,Int32)在指定條件得到滿足或指定超時過期之前自旋。SpinUntil(Func,TimeSpan)在指定條件得到滿足或指定超時過期之前自旋。

自旋示例

下面來實現(xiàn)一個讓當前線程等待其它線程完成任務(wù)的功能。

其功能是開辟一個線程對sum進行+1,當新的線程完成運算后,主線程才能繼續(xù)運行。

classProgram

staticvoidMain(string[]args)

newThread(DoWork).Start();

//等待上面的線程完成工作

MySleep();

Console.WriteLine("sum="+sum);

Console.ReadKey();

privatestaticintsum=0;

privatestaticvoidDoWork()

for(inti=0;i1000_0000;i++)

sum++;

isCompleted=true;

//自定義等待等待

privatestaticboolisCompleted=false;

privatestaticvoidMySleep()

inti=0;

while(!isCompleted)

i++;

}

新的實現(xiàn)

我們改進上面的示例,修改MySleep方法,改成:

privatestaticboolisCompleted=false;

privatestaticvoidMySleep()

SpinWaitwait=newSpinWait();

while(!isCompleted)

wait.SpinOnce();

}

或者改成

privatestaticboolisCompleted=false;

privatestaticvoidMySleep()

SpinWait.SpinUntil(()=isCompleted);

}

SpinLock結(jié)構(gòu)

微軟文檔:提供一個相互排斥鎖基元,在該基元中,嘗試獲取鎖的線程將在重復檢查的循環(huán)中等待,直至該鎖變?yōu)榭捎脼橹埂?/p>

SpinLock稱為自旋鎖,適合用在頻繁爭用而且等待時間較短的場景。主要特征是避免了阻塞,不出現(xiàn)昂貴的上下文切換。

筆者水平有限,關(guān)于SpinLock,可以參考/UploadFile/1d42da/spinlock-class-in-threading-C-Sharp/

另外,還記得Monitor嘛?SpinLock跟Monitor比較像噢~/article/237307.htm

在《C#多線程(10:讀寫鎖)》中,我們介紹了ReaderWriterLock和ReaderWriterLockSlim,而ReaderWriterLockSlim內(nèi)部依賴于SpinLock,并且比ReaderWriterLock快了三倍。

屬性和方法

SpinLock常用屬性和方法如下:

屬性:

屬性說明IsHeld獲取鎖當前是否已由任何線程占用。IsHeldByCurrentThread獲取鎖是否已由當前線程占用。IsThreadOwnerTrackingEnabled獲取是否已為此實例啟用了線程所有權(quán)跟蹤。

方法:

方法說明Enter(Boolean)采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查lockTaken以確定是否已獲取鎖。Exit()釋放鎖。Exit(Boolean)釋放鎖。TryEnter(Boolean)嘗試采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查lockTaken以確定是否已獲取鎖。TryEnter(Int32,Boolean)嘗試采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查lockTaken以確定是否已獲取鎖。TryEnter(TimeSpan,Boolean)嘗試采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查lockTaken以確定是否已獲取鎖。

示例

SpinLock的模板如下:

privatestaticvoidDoWork()

SpinLockspinLock=newSpinLock();

boolisGetLock=false;//是否已獲得了鎖

spinLock.Enter(refisGetLock);

//運算

finally

if(isGetLock)

spinLock.Exit();

}

這里就不寫場景示例了。

需要注意的是,SpinLock實例不能共享,也不能重復使用。

等待性能對比

大佬的文章,.NET中的多種鎖性能測試數(shù)據(jù):/synchronisation-in-net-part-3-spinlocks-and-interlocks/

這里我們簡單測試一下阻塞和自旋的性能測試對比。

我們經(jīng)常說,Thread.Sleep()會發(fā)生上下文切換,出現(xiàn)比較大的性能損失。具體有多大呢?我們來測試一下。(以下運算都是在Debug下測試)

測試Thread.Sleep(1):

privatestaticvoidDoWork()

Stopwatchwatch=newStopwatch();

watch.Start();

for(inti=0;i1_0000;i++)

Thread.Sleep(1);

watch.Stop();

Console.WriteLine(watch.ElapsedMilliseconds);

}

筆者機器測試,結(jié)果大約20018。Thread.Sleep(1)減去等待的時間10000毫秒,那么進行10000次上下文切換需要花費10000毫秒,約每次1毫秒。

上面示例改成:

for(inti=0;i1_000

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論