偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

詳解.NET編程過程中的線程沖突

開發(fā) 后端
今天被問到一個問題,某個函數(shù)在多線程環(huán)境中,會不會有沖突。在幫她解答這個問題的過程中,發(fā)現(xiàn)很多人對線程沖突和線程安全的理解并不是很全面,所以萌發(fā)了寫這么一篇文章的想法,也算是對這個問題的一個完整的解答。

一、什么是線程沖突

線程沖突其實就是指,兩個或以上的線程同時對同一個共享資源進行操作而造成的問題。

一個比較經(jīng)典的例子是,用一個全局變量做計數(shù)器,然后開N個線程去完成某個任務(wù),每個線程完成一次任務(wù)就將計數(shù)器加一,直到完成100次任務(wù)。如果不考慮線程沖突問題,用類似下面的代碼去做,則很可能會超額完成任務(wù),線程越多,完成任務(wù)次數(shù)超出100次的可能性就越大。

偽代碼如下:

int count = 0;//全局計數(shù)器

void ThreadMethod()//運行在每個線程的方法

{

while( true )

{

if ( count >= 100 )//如果達到任務(wù)指標

break;//中斷線程執(zhí)行

DoSomething();//完成某個任務(wù)

count++;

}

}

//省略線程的創(chuàng)建等代碼。

具體的,為什么會超額完成任務(wù)的原因在這里我就不贅述了,這個例子在單線程環(huán)境中是絕對不會超額完成任務(wù)的。

當然,在這個例子中,將count++放到if語句中,也許能降低一些事故發(fā)生的概率,但那不是絕對的,換言之這樣的程序不能杜絕超額完成任務(wù)的可能。

其實從線程沖突的定義中我們不難發(fā)現(xiàn),要造成線程沖突有兩個必要條件:多線程和共享資源。這兩個條件中有一個不成立,就不可能發(fā)生線程沖突問題。

所以,在單線程環(huán)境中,是不存在線程沖突的問題的。不過很可惜的是,我們的軟件早已進化到了多進程多線程的時代,單線程的程序幾乎是不存在的,無論是WinForm還是WebForm,程序運行的環(huán)境都是多線程的,而不論你自己是不是明確的開啟了一個線程。

既然多線程是不可避免的,那么要避免線程沖突就只能從共享資源來開刀了。

二、線程安全的資源

如果大家經(jīng)常看MSDN或者VS幫助中的.NET類庫參考的話,就不難發(fā)現(xiàn)幾乎所有的類型都有這么一句話的描述:“此類型的任何公共 static(在 Visual Basic中為 Shared) 成員都是線程安全的。但不保證所有實例成員都是線程安全的?!蹦敲淳€程安全到底是什么意思?

其實線程安全很簡單,就是指一個函數(shù)(方法、屬性、字段或者別的)在同一時間被不同線程使用,不會造成任何線程沖突的問題。就說這個東西是線程安全的。

接下來來談?wù)勈裁礃拥馁Y源是線程安全的。

之所以使用資源這個詞,是因為線程沖突不僅僅會發(fā)生在共享的變量上,兩個線程同時對同一個文件進行讀寫,兩個程序同時用同一個端口與同一個地址進行通信,都會造成線程沖突。只不過是操作系統(tǒng)和幫我們協(xié)調(diào)了這些沖突而已。

一個線程安全的資源即是指,在不同線程中使用不會導致線程沖突問題的資源。

一個不能被改變的資源是線程安全的,比如說一個常量:

const decimal pai = 3.14159265;

//C++: const double pai = 3.14159265;

因為pai的值不可能被改變,所以在不同的線程中使用也不會造成沖突。換言之它在不同的線程中同時被使用和在一個線程中被使用是沒有區(qū)別的,所以這個東西線程安全的。

同樣的,在.NET中,一個字符串的實例也是線程安全的,因為字符串的實例在.NET中也是不可以被改變的。一個字符串的實例一旦被創(chuàng)建,對其所有的屬性、方法調(diào)用的結(jié)果都是唯一確定的,永遠不會改變的。所以.NET類庫參考中String類型才有:“此類型是線程安全的?!?,與之類似的Type類型、Assembly類型,都是線程安全的。

但string的實例是線程安全的,卻不代表string的變量是線程安全的,換言之,假設(shè)有一個靜態(tài)變量:

public static string str = “123”;

str不是線程安全的,因為str這個變量的字符串實例可以被任何線程修改。

再考慮這樣的例子:

public static readonly SqlConnection connection = new SqlConnection( “connectionString” );

雖然connection本身雖然是線程安全的,但connection的任何成員都不是線程安全的。

比如說,我在一個線程中對這個connection調(diào)用了Open方法,然后進行查詢操作。但在同一時刻,另一個線程調(diào)用了Close方法,這時候,就出現(xiàn)錯誤了。

但,單純的使用connection而不使用其任何成員,比如說if ( connection != null )這樣的代碼,是不存在線程沖突的。

線程安全的資源其實還有很多,在此不一一贅述。

對于.NET Framework的類型的成員來說,只讀的字段是線程安全的。

那么對于屬性和方法來說,怎么知道是不是線程安全的?

三、線程安全的函數(shù)

因為屬性和方法都是函數(shù)組成的,所以我們探討一下什么是線程安全的函數(shù)。

上面我們說到,線程沖突的必要條件是多線程和共享資源。那么如果一個函數(shù)里面沒有使用任何可能共享的資源,那么就不可能出現(xiàn)線程沖突,也就是線程安全的。比如說這樣的函數(shù):

public static int Add( int a, int b )

{

return a + b;

}

這個函數(shù)中所使用的所有的資源都是自己的局部變量,而函數(shù)的局部變量是儲存在堆棧上的,每個線程都有自己獨立的堆棧,所以局部變量不可能跨線程共享。所以這樣的函數(shù)顯然是線程安全的。

但值得注意的是:下面的函數(shù)不是線程安全的:

public static void Swap( ref int a, ref int b )

//C++: void Swap( in& a, int& b )

{

int c = a;

a = b;

b = c;

}

因為ref的存在,使得函數(shù)的參數(shù)是按引用傳遞進來的,換言之a(chǎn)和b看起來是函數(shù)的局部變量,但實際上卻是函數(shù)外面的東西,如果這兩個東西是另一個函數(shù)的局部變量,倒也沒有問題,如果這兩個東西是全局變量(靜態(tài)成員),就不能確保沒有線程沖突了。而在上個例子中,a和b在傳入函數(shù)之時,就做了一個拷貝的動作,所以傳進來的a、b到底是全局變量還是靜態(tài)成員都沒有關(guān)系了。

同樣,這樣的函數(shù)也不是線程安全的:

public static int Add( INumber a, INumber b )

//C++: int Add( INumber* a, INumber* b );

{

return a.Number + b.Number;

//C++: return a->Number + b->Number;

}

原因在于a和b雖然是函數(shù)的內(nèi)部變量沒錯,但a.Number和b.Number卻不是,它們不存在于堆棧上,而是在托管堆上,可能被其他線程更改。

但只使用局部變量的函數(shù)在.NET類庫中是很少的,但.NET類庫中還是有那么多線程安全的函數(shù),是為什么呢?

因為,即使一個函數(shù)使用了共享資源,如果其所使用的共享資源都是線程安全的,則這個函數(shù)也是線程安全的。

比如說這樣的函數(shù):

private const string connectionString = “…”;

public string GetConnectionString()

{

return connectionString;

}

雖然這個函數(shù)使用了一個共享資源connectionString,但因為這個資源是線程安全的,所以這個函數(shù)還是線程安全的。

同樣的,我們可以得出,如果一個函數(shù)只調(diào)用線程安全的函數(shù),只使用線程安全的共享資源,那么這個函數(shù)也是線程安全的。

這里有一個容易被忽略的問題,運算符。并不是所有的運算符(尤其是重載后的運算符)都是線程安全的。

四、互斥鎖

有時候我們不得不面對線程不安全的問題,比如說在一開始提出來的那個例子,多線程完成100次任務(wù),我們怎樣才能解決這個問題,一個簡單的辦法就是給共享資源加上互斥鎖。在C#中這很簡單。比如一開始的那個例子:

public static class Environment

{public static int count = 0;//全局計數(shù)器

}

//…

void ThreadMethod()//運行在每個線程的方法

{

while( true )

{

lock ( typeof( Environment ) )

{

if ( count >= 100 )//如果達到任務(wù)指標

break;//中斷線程執(zhí)行

DoSomething();//完成某個任務(wù)

count++;}}}

通過互斥鎖,使得一個線程在使用count字段的時候,其他所有的線程都無法使用,而被阻塞等待。達到了避免線程沖突的效果。

當然,這樣的鎖會使得這個多線程程序退化成同時只有一個線程在跑,所以我們可以把count++提前,使得lock的范圍縮小,如這樣:

void ThreadMethod()//運行在每個線程的方法

{

while( true )

{

lock ( typeof( Environment ) )

{

if ( count++ >= 100 )//如果達到任務(wù)指標

break;//中斷線程執(zhí)行

}

DoSomething();//完成某個任務(wù)

}}

最后來聊聊SyncRoot的問題。

用.NET的一定會有很多朋友困惑,為什么對一個容器加鎖,需要這樣寫:

lock( Container.SyncRoot )

而不是直接lock( Container )

因為鎖定一個容器并不能保證不會對這個容器進行修改,考慮這樣一個容器:

public class Collection

{

private ArrayList _list;

public Add( object item )

{

_list.Add( item );

}

public object this[ int index ]

{

get { return _list[index]; }

set { _list[index] = value;}

}}

看起來,將其lock起來后,就萬事大吉了,沒有人能修改這個容器,但實際上這個容器不過是用一個ArrayList實例來實現(xiàn)的,如果某段代碼繞過這個容器而直接操作_list的話,則對這個容器對象lock也不可能保證容器不被修改了。

【編輯推薦】

  1. 淺談.NET開發(fā)中多線程思維方式
  2. C#中使用多線程訪問Winform問題解決方案
  3. .NET上執(zhí)行多線程應(yīng)該注意的兩點
責任編輯:彭凡 來源: cnblogs
相關(guān)推薦

2011-05-03 10:31:59

噴墨打印機注墨誤區(qū)

2024-12-02 00:57:17

非阻塞異步編程

2012-01-17 16:41:08

JavaSwing

2011-06-28 09:39:31

Qt

2021-03-05 14:49:28

編程語言工具

2011-01-26 09:40:42

.NET開發(fā)

2010-06-04 17:43:12

Hadoop集群搭建

2015-04-22 09:50:56

程序員

2010-03-15 09:11:25

Python編程版面

2009-03-23 10:54:12

.NET契約式編程編程思想

2012-06-25 10:13:00

Java.NET

2009-10-14 09:27:15

結(jié)構(gòu)化綜合布線

2015-07-27 09:53:13

PHP編程過程

2022-09-06 08:25:13

線程異步任務(wù)

2010-03-16 15:57:26

Python二維數(shù)組

2009-07-23 14:10:38

Hibernate J

2013-08-15 12:26:40

阿里云飛天

2010-07-01 14:05:43

SNMPMIB

2011-04-11 17:28:50

oracle存儲select語句

2025-06-25 09:06:18

點贊
收藏

51CTO技術(shù)棧公眾號