C#多線程編程中的鎖系統(tǒng)(二)
上章主要講排他鎖的直接使用方式。但實(shí)際當(dāng)中全部都用鎖又太浪費(fèi)了,或者排他鎖粒度太大了。 這一次我們說(shuō)說(shuō)升級(jí)鎖和原子操作。
目錄
1:volatile
2: Interlocked
3:ReaderWriterLockSlim
4:總結(jié)
一:volatile
簡(jiǎn)單來(lái)說(shuō): volatile關(guān)鍵字是告訴c#編譯器和JIT編譯器,不對(duì)volatile標(biāo)記的字段做任何的緩存。確保字段讀寫都是原子操作,最新值。
這不就是鎖嗎? 其這貨它根本不是鎖, 它的原子操作是基于CPU本身的,非阻塞的。 因?yàn)?2位CPU執(zhí)行賦值指令,數(shù)據(jù)傳輸最大寬度4個(gè)字節(jié)。
所以只要在4個(gè)字節(jié)以下讀寫操作的,32位CPU都是原子操作。volatile 它就是利用這個(gè)特性來(lái)的。
好殘酷的事實(shí)?不然,微軟大法這樣是為了提高JIT性能效率,對(duì)有些數(shù)據(jù)進(jìn)行緩存了(多線程下)。
//正確
public volatile Int32 score1 = 1;
//報(bào)錯(cuò)
public volatile Int64 score2 = 1;
看上面的例子,我們定義8個(gè)字節(jié)長(zhǎng)度score2就不行了。 因?yàn)?個(gè)字節(jié),32位CPU就分成2個(gè)指令執(zhí)行了。自然就無(wú)法保證原子操作了。
這么細(xì)節(jié)的,忘了怎么辦,那豈不是坑人啊。 于是微軟大法直接一棍子打死,限制4個(gè)字節(jié)以下的類型字段才能用volatile,具體什么、看msdn吧。
那今天我知道了。我編譯平臺(tái)改成64位上,只在64位CPU用volatile int64,行不行? 不行,編譯器報(bào)錯(cuò)。說(shuō)了一棍子打死了。。
(^._.^)ノ 好吧,其實(shí)可以用IntPtr這個(gè)。
volatile多數(shù)情況下很有用處的,畢竟鎖的性能開(kāi)銷還是很大的。我們可以把當(dāng)成輕量級(jí)的鎖,根據(jù)具體場(chǎng)景合理使用,能提高不少程序性能。
線程中的Thread.VolatileRead 和Thread.VolatileWrite 就是volatile的復(fù)雜版。
二:Interlocked
MSDN 描述:為多個(gè)線程共享的變量提供原子操作。主要函數(shù)如下:
Interlocked.Increment 原子操作,遞增指定變量的值并存儲(chǔ)結(jié)果。
Interlocked.Decrement 原子操作,遞減指定變量的值并存儲(chǔ)結(jié)果。
Interlocked.Add 原子操作,添加兩個(gè)整數(shù)并用兩者的和替換第一個(gè)整數(shù)
Interlocked.CompareExchange(ref a, b, c); 原子操作,a參數(shù)和c參數(shù)比較, 相等b替換a,不相等不替換。
基本用法就不多說(shuō)了。直接來(lái)段CLR via C# interlock anything的例子:
public static int Maximum(ref int target, int value)
{
int currentVal = target, startVal, desiredVal; //記錄前后值
do
{
startVal = currentVal; //記錄循環(huán)迭代的初始值。
desiredVal = Math.Max(startVal, value); //基于startVal和value計(jì)算期望值desiredVal
//高并發(fā)下,線程被搶占情況下,target值會(huì)發(fā)生改變。
//target startVal相等說(shuō)明沒(méi)改變。desiredVal 直接替換。
currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);
} while (startVal != currentVal); //不相等說(shuō)明,target值已經(jīng)被其他線程改動(dòng)。自旋繼續(xù)。
return desiredVal;
}
三:ReaderWriterLockSlim
假如我們有份緩存數(shù)據(jù)A,如果每次都不管任何操作lock一下,那么我的這份緩存A就永遠(yuǎn)只能單線程讀寫了, 這在Web高并發(fā)下是不能忍受的。
那有沒(méi)有一種辦法我只在寫入時(shí)進(jìn)入獨(dú)占鎖呢,讀操作時(shí)不限制線程數(shù)量呢?答案就是我們的ReaderWriterLockSlim主角,讀寫鎖。
ReaderWriterLockSlim 其中一種鎖EnterUpgradeableReadLock最關(guān)鍵 即可升級(jí)鎖。
它呢允許你先進(jìn)入讀鎖,發(fā)現(xiàn)緩存A不一樣了, 再進(jìn)入寫鎖,寫入后退回讀鎖模式。
ps: 這里注意下net 3.5之前有個(gè)ReaderWriterLock 性能較差。推薦使用升級(jí)版的 ReaderWriterLockSlim 。
//實(shí)例一個(gè)讀寫鎖
ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
上面實(shí)例一個(gè)讀寫鎖,這里注意的是構(gòu)造函數(shù)的枚舉。
LockRecursionPolicy.NoRecursion 不支持,發(fā)現(xiàn)遞歸會(huì)拋異常。
LockRecursionPolicy.SupportsRecursion 即支持遞歸模式,線程鎖中繼續(xù)在使用鎖。
cacheLock.EnterReadLock();
//do
cacheLock.EnterReadLock();
//do
cacheLock.ExitReadLock();
cacheLock.ExitReadLock();
這種模式極易容易死鎖,比如讀鎖里面使用寫鎖。
cacheLock.EnterReadLock();
//do
cacheLock.EnterWriteLock();
//do
cacheLock.ExitWriteLock();
cacheLock.ExitReadLock();
下面是直接拿msdn的緩存例子了,加了簡(jiǎn)單注釋。
public class SynchronizedCache
{
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary<int, string> innerCache = new Dictionary<int, string>();
public string Read(int key)
{
//進(jìn)入讀鎖,允許其他所有的讀線程,寫入線程被阻塞。
cacheLock.EnterReadLock();
try
{
return innerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
public void Add(int key, string value)
{
//進(jìn)入寫鎖,其他所有訪問(wèn)操作的線程都被阻塞。即寫?yīng)氄兼i。
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public bool AddWithTimeout(int key, string value, int timeout)
{
//超時(shí)設(shè)置,如果在超時(shí)時(shí)間內(nèi),其他寫鎖還不釋放,就放棄操作。
if (cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
//進(jìn)入升級(jí)鎖。 同時(shí)只能有一個(gè)可升級(jí)鎖線程。寫鎖,升級(jí)鎖都被阻塞,但允許其他讀取數(shù)據(jù)的線程。
cacheLock.EnterUpgradeableReadLock();
try
{
string result = null;
if (innerCache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
//升級(jí)成寫鎖,其他所有線程都被阻塞。
cacheLock.EnterWriteLock();
try
{
innerCache[key] = value;
}
finally
{
//退出寫鎖,允許其他讀線程。
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
//退出升級(jí)鎖。
cacheLock.ExitUpgradeableReadLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
}
四:總結(jié)
多線程實(shí)際開(kāi)發(fā)當(dāng)中,往往測(cè)試沒(méi)問(wèn)題,一到生產(chǎn)環(huán)境,并發(fā)高了就容易出問(wèn)題, 一定注意。
本文參考CLR via C#。
- C#多線程中的互斥鎖Mutex
- C#多線程死鎖介紹與案例代碼
- C#在復(fù)雜多線程環(huán)境下使用讀寫鎖同步寫入文件
- C#使用讀寫鎖解決多線程并發(fā)問(wèn)題
- C#多線程系列之讀寫鎖
- C#多線程系列之多線程鎖lock和Monitor
- c#多線程之間的排他鎖的實(shí)現(xiàn)
- C#筆試題之同線程Lock語(yǔ)句遞歸不會(huì)死鎖
- C#多線程中如何運(yùn)用互斥鎖Mutex
- C#使用讀寫鎖三行代碼簡(jiǎn)單解決多線程并發(fā)的問(wèn)題
- C#多線程編程中的鎖系統(tǒng)(四):自旋鎖
- C#多線程編程中的鎖系統(tǒng)(三)
- C#多線程編程中的鎖系統(tǒng)基本用法
- C#多線程之線程鎖
相關(guān)文章
C#實(shí)現(xiàn)撲克游戲(21點(diǎn))的示例代碼
21點(diǎn)又名黑杰克,該游戲由2到6個(gè)人玩,使用除大小王之外的52張牌,游戲者的目標(biāo)是使手中的牌的點(diǎn)數(shù)之和不超過(guò)21點(diǎn)且盡量大。本文將用C#實(shí)現(xiàn)這一經(jīng)典游戲,需要的可以參考一下2022-08-08C#分布式事務(wù)的超時(shí)處理實(shí)例分析
這篇文章主要介紹了C#分布式事務(wù)的超時(shí)處理,以實(shí)例形式較為詳細(xì)的分析了C#分布式事務(wù)的超時(shí)處理相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03DevExpress之ChartControl的SeriesTemplate實(shí)例
這篇文章主要介紹了DevExpress之ChartControl的SeriesTemplate用法實(shí)例,實(shí)現(xiàn)了餅狀Series百分比顯示的效果,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-10-10C#實(shí)現(xiàn)簡(jiǎn)單獲取及設(shè)置Session類
這篇文章主要介紹了C#實(shí)現(xiàn)簡(jiǎn)單獲取及設(shè)置Session類,涉及C#針對(duì)session的設(shè)置及獲取的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03C# 指針內(nèi)存控制Marshal內(nèi)存數(shù)據(jù)存儲(chǔ)原理分析
這篇文章主要介紹了C# 指針 內(nèi)存控制 Marshal 內(nèi)存數(shù)據(jù)存儲(chǔ)原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02C#使用NPOI讀取excel轉(zhuǎn)為DataSet
這篇文章主要為大家詳細(xì)介紹了C#使用NPOI讀取excel轉(zhuǎn)為DataSet,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C#使用whisper.net實(shí)現(xiàn)語(yǔ)音識(shí)別功能
這篇文章主要為大家詳細(xì)介紹了C#如何使用whisper.net實(shí)現(xiàn)語(yǔ)音識(shí)別功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以學(xué)習(xí)一下2023-11-11C#從foreach語(yǔ)句中枚舉元素看數(shù)組詳解
這篇文章主要給大家介紹了關(guān)于C#從foreach語(yǔ)句中枚舉元素看數(shù)組的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-05-05