日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

C#多线程编程(6)--线程安全2 互锁构造Interlocked

發(fā)布時(shí)間:2025/4/16 C# 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#多线程编程(6)--线程安全2 互锁构造Interlocked 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在線程安全1中,我介紹了線程同步的意義和一種實(shí)現(xiàn)線程同步的方法:volatile。volatile關(guān)鍵字屬于原子操作的一種,若對(duì)一個(gè)關(guān)鍵字使用volatile,很多時(shí)候會(huì)顯得很“浪費(fèi)”,因?yàn)橹挥性诓l(fā)訪問的情況下才需要“易變”讀寫,單線程訪問時(shí)并不需要。在命名空間System.Threading命名空間中提供了InterLock類,該類中提供了一些原子方法。本文來(lái)介紹如何使用這些方法。

  •   互鎖

  在搶占式系統(tǒng)中,一個(gè)線程在執(zhí)行到任何階段都有可能被其他線程“打斷”,原子操作能夠保證該操作不被其他線程打斷,其他線程的“打斷”只可能發(fā)生在該操作的之前或之后。InterLock類中的方法就是”原子“式的,最常用的方法有:

public static class InterLock{// return (++location)public static int Increment(ref int location);// return(--location)public static int Decrement(ref int location);// return(location += value)//注意,也可能是負(fù)數(shù),從而實(shí)現(xiàn)減法運(yùn)算。public static int Add(ref int location, int value)// int old = location; location = value; return old;public static int Exchange(ref int location, int value);//old = location1;//if(location1 = comparand) location1 = value;//return old;public static int CompareExchange(ref int location1,int value, int comparand);... }

  《CLR via C#》的作者在書中說(shuō)他喜歡Interlocked中的方法,因?yàn)樗坏芸?#xff0c;而且不會(huì)阻塞線程。為了驗(yàn)證InterLock真的很快,我們對(duì)變量進(jìn)行一百萬(wàn)次的寫,與Volatile.Write()來(lái)進(jìn)行對(duì)比,看看是否真的快。

static void Main(string[] args){TestInterLock();Console.ReadLine();}private static volatile int v = 0;private static int n = 0;static void TestInterLock(){Stopwatch sw = Stopwatch.StartNew();for (int i = 0; i < 1000000; i++)v++;Console.WriteLine("volatile write 1000000 times takes:{0}", sw.ElapsedMilliseconds);sw = Stopwatch.StartNew();for (int i = 0; i < 1000000; i++)Interlocked.Increment(ref n);Console.WriteLine("InterLock write 1000000 times takes:{0}", sw.ElapsedMilliseconds);n = 0;sw = Stopwatch.StartNew();for (int i = 0; i < 1000000; i++)n++;Console.WriteLine("n++ write 1000000 times takes:{0}", sw.ElapsedMilliseconds);}

?

  運(yùn)行結(jié)果如下:

volatile write 1000000 times takes:2

InterLock write 1000000 times takes:8

n++ write 1000000 times takes:2

  我運(yùn)行了好幾次,結(jié)果會(huì)有些出入,但是大部分的結(jié)果都是volatile的寫入速度和原生的n++的速度是一樣的。InterLock也確實(shí)如Jeffrey Richter所說(shuō),很快,只是沒有volatile關(guān)鍵字修飾的變量的讀寫快。這是在非并發(fā)情況下,下面來(lái)看一下并發(fā)情況下是否還是很快。

static void TestInterLock1(){Stopwatch sw = Stopwatch.StartNew();Task[] t2 = new[] { new Task(() => {for (int i = 0; i < 1000000; i++)Interlocked.Increment(ref n);}), new Task(() => { for (int i = 0; i < 1000000; i++)Interlocked.Increment(ref n);}) };Task[] t1 = new[] { new Task(() => {for (int i = 0; i < 1000000; i++)v++;}), new Task(() => { for (int i = 0; i < 1000000; i++)v++;}) };Task.WhenAll(t1).ContinueWith(t => Console.WriteLine("volatile write 2000000 times takes:{0}", sw.ElapsedMilliseconds));Task.WhenAll(t2).ContinueWith(t => Console.WriteLine("InterLock write 2000000 times takes:{0}", sw.ElapsedMilliseconds));t2[0].Start();t1[0].Start();t1[1].Start();t2[1].Start(); }

  先看運(yùn)行結(jié)果,

volatile write 2000000 times takes:94
InterLock write 2000000 times takes:101

  多次運(yùn)行,運(yùn)行時(shí)間會(huì)有不同,但是在并發(fā)情況下,volatile的寫入和InterLock的寫入速度幾乎相同。上述代碼寫的如此丑陋,而不是直接寫Task.Run(),是為了保證初始化部分都運(yùn)行完成后,再Start(),且兩個(gè)任務(wù)的先后順序進(jìn)行了打亂,最大限度減少誤差。可以看到并發(fā)情況下,volatile和InterLock幾乎一樣,且在InterLock中的方法要比Volatile的功能要全,但是在串行時(shí),Volatile的性能要比InterLock要好。結(jié)論是,若只對(duì)變量讀寫,沒有替換或者其他復(fù)雜操作時(shí),可以使用volatile關(guān)鍵字,但是一些復(fù)雜操作,需要原子操作時(shí),就得使用InterLock中的方法了,如果使用volatile關(guān)鍵字修飾的變量來(lái)進(jìn)行交換的話,很難保證原子性,只有引入鎖才能保證線程同步。且InterLock中提供了幾個(gè)重載方法,能夠接受object類型,還有泛型版本。

可以利用InterLock來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的自旋鎖,代碼如下:

public struct SimpleSpinLock{private int m_Lock;public void Enter(){while (true){if (Interlocked.Exchange(ref m_Lock, 1) == 0) return;//此處可以添加黑科技 }}public void Leave(){Volatile.Write(ref m_Lock, 0);} }//下面是如何使用SimpleSpinLock的例子 public class Simple{private SimpleSpinLock m_lock = new SimpleSpinLock();public void AccessResource(){m_lock.Enter();//執(zhí)行某些程序,只有一個(gè)線程可以進(jìn)入這里 m_lock.Leave();} }

  這個(gè)簡(jiǎn)單的自旋鎖在一個(gè)線程調(diào)用Enter()時(shí),其他線程在調(diào)用m_lock.Enter()方法時(shí),if (Interlocked.Exchange(ref m_lock, 1) == 0)會(huì)失敗,因?yàn)樵摲椒〞?huì)將m_lock和1交換,并返回舊值,在已有線程調(diào)用m_lock.Enter()時(shí),m_lock的舊值是1,因此該方法會(huì)在whle(true)處自旋,不斷嘗試獲得鎖。該鎖的問題是,該線程沒有被阻塞(掛起),而是一直在占用CPU資源,其他需要CPU資源的線程無(wú)法運(yùn)行(可以在while內(nèi),我加注釋的地方,加入”黑科技“來(lái)嘗試解決此問題。其思路是在線程自旋的過程中,立刻交出CPU資源,可通過調(diào)用Thread.Sleep(0)或者Thread.Yield()來(lái)實(shí)現(xiàn)。嘗試獲得鎖的線程交出時(shí)間片,這樣當(dāng)前獲得鎖的線程能夠有更多的資源來(lái)運(yùn)行程序,從而運(yùn)行結(jié)束并交出鎖,具體細(xì)節(jié)在這里不展開)。因此,自旋鎖只適合那些運(yùn)行非常快的方法。

  • Interlocked Anything 模式

  Interlocked中全部是原子性操作,那是否提供了一個(gè)方法,該方法可以接受一個(gè)委托,保證該委托在運(yùn)行時(shí)是原子的。答案是沒有,但是可以利用Interlocked.CompareExchange來(lái)自己實(shí)現(xiàn)一個(gè)。我們先來(lái)看一下利用CompareExchange來(lái)實(shí)現(xiàn)原子性的Maximum方法。

public static int Maximum(ref int target, int value){int currentValue = target, startValue, desireValue;do{startValue = currentValue;//可以在此處添加任何想要保證“原子性”的操作,此處是求最大值。desireValue = Math.Max(startValue, value);//注意,此處有可能被其他線程搶占,也有可能target的值被修改,因此if()語(yǔ)句會(huì)出問題,要使用Interlocked的方法。//if (startValue = target) target = desireValue;currentValue = Interlocked.CompareExchange(ref target, desireValue, startValue);} //若在此時(shí)target已被修改,則重新計(jì)算最大值while (startValue != currentValue);return currentValue; }

  此方法的思路是:從CPU將target的值讀到寄存器中,到計(jì)算最大值結(jié)束,期間的任何時(shí)間target都有可能被其他線程修改。因此保證原子性就被轉(zhuǎn)換成保證計(jì)算最大值時(shí),target的值沒有變過,如果變過,就重新計(jì)算。因此,在最開始的時(shí)候,startValue=currentValue,currentValue是開始計(jì)算時(shí)target的值。然后求得最大值,并保存到desireValue中。注意,此時(shí)target有可能被修改,因此調(diào)用CompareExchange方法,該方法會(huì)將target與startValue比較,如果此時(shí)兩值相等,那相當(dāng)于我之前說(shuō)的,target從開始到最后沒有改變,那么這個(gè)最大值是準(zhǔn)確的,并將target的舊值付給currentValue,最后如果startValue==currentValue,則計(jì)算完成,否則繼續(xù)循環(huán)。

  《CLR via C#》的作者Jeff很喜歡上面的方法,他在實(shí)際開發(fā)中,都是使用上面的方法,并對(duì)其進(jìn)行了包裝,使之能夠支持Interlocked Anything。其原理就是在desireValue=Math.Max()處替換成其他方法,只要在返回結(jié)果時(shí)保證舊值和最開始讀取的值一致就可以。我們來(lái)看一下他的封裝:

delegate int Morpher<TResult, in TArgument>(int startValue, TArgument argument, out TResult result);static TResult Morph<TResult, TArgumen>(ref int target, TArgumen argumen, Morpher<TResult, TArgumen> morpher){TResult mophorResult;int currentValue = target, desireValue, startValue;do{startValue = currentValue;desireValue = morpher(startValue, argumen, out mophorResult);currentValue = Interlocked.CompareExchange(ref target, desireValue, startValue);} while (currentValue != startValue);return mophorResult; }

說(shuō)實(shí)話,我并不能非常好了理解這個(gè)封裝,并不是不能理解做法,而是不能確定此方法到底能不能實(shí)現(xiàn)效果,那我們來(lái)測(cè)試一下。測(cè)試的基本思路是對(duì)一個(gè)變量執(zhí)行1000次的result+=10。分別是不帶線程同步的和利用Morph方法對(duì)result+=10的方法進(jìn)行互鎖,保證其原子性。我省去了Morpher和Morph的聲明部分。之所以要在DelayAdd和DelayAdd1方法中調(diào)用ThreadSleep(20),是為了模擬當(dāng)在運(yùn)行較長(zhǎng)的方法時(shí),Morph方法是否還能夠保證該方法運(yùn)行的原子性。

static void Main(string[] args){Test(new TestAction(Add), "Add");Test(new TestAction(MorphAdd), "MorphAdd");Console.ReadLine(); } //DelayAdd1的變種,使之能夠符合Morpher的簽名 static int DelayAdd(int startValue, int argument, out int result){Thread.Sleep(20);result = startValue + argument;return result; }static void DelayAdd1(int argument, ref int result){Thread.Sleep(20);result += argument; } //測(cè)試的不具有線程同步的方法。 static void Add(ref int result){DelayAdd1(10, ref result); } //具有線程同步的方法。 static void MorphAdd(ref int result){Morph(ref result, 10, new Morpher<int, int>(DelayAdd)); } //要測(cè)試的委托簽名 delegate void TestAction(ref int result); //公共測(cè)試方法 static void Test(TestAction action, string actionName){int result = 0;var tList = new Task[1000];for (int i = 0; i < 1000; i++)tList[i] = Task.Run(() =>{action(ref result);});Task.WhenAll(tList).GetAwaiter().OnCompleted(() => Console.WriteLine("{0}, Result is {1}", actionName, result)); }

運(yùn)行,得到的結(jié)果是:

Add, Result is 8440
MorphAdd, Result is 10000

運(yùn)行1000次result += 10,普通的Add不能夠得到正確結(jié)果,但是MorphAdd可以。這是因?yàn)樵?000次的Add中,某幾個(gè)Add是同時(shí)調(diào)用的,result+=10在同一時(shí)間調(diào)用了多次,因此有156次的Add因?yàn)椴⑿卸煌淌闪恕V档米⒁獾氖?#xff0c;MorphAdd方法因?yàn)樾枰€程同步,因此執(zhí)行時(shí)間要慢很多。但是這些付出是值得的,因?yàn)檫@保證了結(jié)果的正確。

  上述例子證明了Morph方法能夠保證委托的原子性,且該方法既不會(huì)阻塞線程也不會(huì)長(zhǎng)時(shí)間的自旋,推薦大家在實(shí)際中使用該方法。

  本文中,我先介紹了Interlocked類中較常用的方法,以及Interlocked.Increment()方法與volatile關(guān)鍵字的對(duì)比,結(jié)論是雖然將變量設(shè)置為所有讀寫都是“易變的”看起來(lái)很浪費(fèi),但是該關(guān)鍵字能夠保證在單線程時(shí)幾乎沒有性能損失,大部分情況下和原生的讀寫是一樣的速度,且volatile比Interlocked類中提供的寫要快2-4倍,但是在并發(fā)狀態(tài)下其性能和volatile關(guān)鍵字是沒有差別的。之后我介紹了用Interlocked類中的方法來(lái)實(shí)現(xiàn)簡(jiǎn)單的自旋鎖,該鎖的優(yōu)點(diǎn)是在非竟態(tài)情況下非常快,但是在竟態(tài)情況下,未獲得鎖的線程會(huì)一直處于自旋狀態(tài),白白浪費(fèi)CPU。最后介紹了《CLR via C#》書中提到的Interlocked Anything的方法(文中其他的知識(shí)點(diǎn)大多也是提取自《CLR via C#》),并測(cè)試了該方法確實(shí)可以保證委托的原子性,且不會(huì)阻塞線程,沒有鎖,不會(huì)造成死鎖。至此線程同步中互鎖構(gòu)造就講完了,后面我會(huì)給大家介紹內(nèi)核構(gòu)造的信號(hào)量和其他鎖。

轉(zhuǎn)載于:https://www.cnblogs.com/jazzpop/p/8547880.html

總結(jié)

以上是生活随笔為你收集整理的C#多线程编程(6)--线程安全2 互锁构造Interlocked的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。