女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

什么是線程安全 如何實現(xiàn)線程安全代碼

Q4MP_gh_c472c21 ? 來源:碼農(nóng)的荒島求生 ? 作者:碼農(nóng)的荒島求生 ? 2022-05-17 12:45 ? 次閱讀

相信有很多同學(xué)在面對多線程代碼時都會望而生畏,認(rèn)為多線程代碼就像一難以馴服的怪獸,你制服不了這頭怪獸它就會反過來吞噬你。

夸張了哈,總之,多線程程序有時就像一潭淤泥,走不進去退不出來。

可這是為什么呢?為什么多線程代碼如此難以正確編寫呢

從根源上思考

關(guān)于這個問題,本質(zhì)上是有一個詞語你沒有透徹理解,這個詞就是所謂的線程安全,thread safe。如果你不能理解線程安全,那么給你再多的方案也是無用武之地接下來我們了解一下什么是線程安全,怎樣才能做到線程安全。這些問題解答后,多線程這頭大怪獸自然就會變成溫順的小貓咪。

可上圖關(guān)小貓咪屁事!

關(guān)你什么屁事

生活中我們口頭上經(jīng)常說的一句話就是“關(guān)你屁事”,大家想一想,為什么我們的屁事不關(guān)別人?原因很簡單,這是我的私事啊!我的衣服、我的電腦,我的手機、我的車子、我的別墅以及私人泳池(可以沒有,但不妨礙想象),我想怎么處理就怎么處理,妨礙不到別人,只屬于我一個人的東西以及事情當(dāng)然不關(guān)別人,即使是屁事也不關(guān)別人 我們在自己家里想吃什么吃什么,想去廁所就去廁所!因為這些都是我私有的,只有我自己使用那么,什么時候會和其它人有交集呢?答案就是公共場所在公共場所下你不能像在自己家里一樣想去哪就去哪,想什么時候去廁所就去廁所,為什么呢?原因很簡單,因為公共場所下的飯館、衛(wèi)生間不是你家的,這是公共資源,大家都可以使用的公共資源。如果你想去飯館、去公共衛(wèi)生間那么就必須遵守規(guī)則,這個規(guī)則就是排隊,只有前一個人用完公共資源后下一個人才可以使用,而且不能同時使用,想使用就必須排隊等待上面這段話道理足夠簡單吧。

如果你能理解這段話,那么馴服多線程這頭小怪獸就不在話下。

維護公共場所秩序

如果把你自己理解為線程的話,那么在你自己家里使用私有資源就是所謂的線程安全,原因很簡單,因為你隨便怎么折騰自己的東西(資源)都不會妨礙到別人但到公共場所浪的話就不一樣了,在公共場所使用的是公共資源,這時你就不能像在自己家里一樣想怎么用就怎么用想什么時候用就什么時候用,公共場所必須有相應(yīng)規(guī)則,這里的規(guī)則通常是排隊,只有這樣公共場所的秩序才不會被破壞,線程以某種不妨礙到其它線程的秩序使用共享資源就能實現(xiàn)線程安全。 因此我們可以看到,這里有兩種情況:
  • 線程私有資源,沒有線程安全問題

  • 共享資源,線程間以某種秩序使用共享資源也能實現(xiàn)線程安全。

本文都是圍繞著上述兩個核心點來講解的,現(xiàn)在我們就可以正式的聊聊編程中的線程安全了。

什么是線程安全

我們說一段代碼是線程安全的,當(dāng)且僅當(dāng)我們在多個線程中同時且多次調(diào)用的這段代碼都能給出正確的結(jié)果,這樣的代碼我們才說是線程安全代碼,Thread Safety,否則就不是線程安全代碼,thread-unsafe.。非線程安全的代碼其運行結(jié)果是由擲骰子決定的。

怎么樣,線程安全的定義很簡單吧,也就是說你的代碼不管是在單個線程還是多個線程中被執(zhí)行都應(yīng)該能給出正確的運行結(jié)果,這樣的代碼是不會出現(xiàn)多線程問題的,就像下面這段代碼:

int func() {  int a = 1;  int b = 1;  return a + b;}

對于這樣段代碼,無論你用多少線程同時調(diào)用、怎么調(diào)用、什么時候調(diào)用都會返回2,這段代碼就是線程安全的。

那么,我們該怎樣寫出線程安全的代碼呢?要回答這個問題,我們需要知道我們的代碼什么時候呆在自己家里使用私有資源,什么時候去公共場所浪使用公共資源,也就是說你需要識別線程的私有資源和共享資源都有哪些,這是解決線程安全問題的核心所在。

2f9df1f4-d597-11ec-bce3-dac502259ad0.png

線程私有資源

線程都有哪些私有資源呢?啊哈,我們在上一篇《線程到底共享了哪些進程資源》中詳細講解了這個問題。線程運行的本質(zhì)其實就是函數(shù)的執(zhí)行,函數(shù)的執(zhí)行總會有一個源頭,這個源頭就是所謂的入口函數(shù),CPU從入口函數(shù)開始執(zhí)行從而形成一個執(zhí)行流,只不過我們?nèi)藶榈慕o執(zhí)行流起一個名字,這個名字就叫線程。既然線程運行的本質(zhì)就是函數(shù)的執(zhí)行,那么函數(shù)運行時信息都保存在哪里呢答案就是棧區(qū),每個線程都有一個私有的棧區(qū),因此在棧上分配的局部變量就是線程私有的,無論我們怎樣使用這些局部變量都不管其它線程屁事。

2fc063ce-d597-11ec-bce3-dac502259ad0.png

線程私有的棧區(qū)就是線程自己家

線程間共享數(shù)據(jù)

除了上一節(jié)提到的剩下的區(qū)域就是公共場合了,這包括:
  • 用于動態(tài)分配內(nèi)存的堆區(qū),我們用C/C++中的malloc或者new就是在堆區(qū)上申請的內(nèi)存

  • 全局區(qū),這里存放的就是全局變量

  • 文件,我們知道線程是共享進程打開的文件

2fd4d5de-d597-11ec-bce3-dac502259ad0.png

有的同學(xué)可能說,等等,在上一篇文章不是說還有代碼區(qū)和動態(tài)鏈接庫嗎?要知道這兩個區(qū)域是不能被修改的,也就是說這兩個區(qū)域是只讀的,因此多個線程使用是沒有問題的。在剛才我們提到的堆區(qū)、數(shù)據(jù)區(qū)以及文件,這些就是所有的線程都可以共享的資源,也就是公共場所,線程在這些公共場所就不能隨便浪了。線程使用這些共享資源必須要遵守秩序,這個秩序的核心就是對共享資源的使用不能妨礙到其它線程,無論你使用各種鎖也好、信號量也罷,其目的都是在維護公共場所的秩序。知道了哪些是線程私有的,哪些是線程間共享的,接下來就簡單了。值得注意的是,關(guān)于線程安全的一切問題全部圍繞著線程私有數(shù)據(jù)與線程共享數(shù)據(jù)來處理,抓住了線程私有資源和共享資源這個主要矛盾也就抓住了解決線程安全問題的核心

接下來,我們看一下在各種情況下該怎樣實現(xiàn)線程安全。這里依然以C/C++代碼為例,但是這里講解的方法適用于任何語言,請放心,這些代碼足夠簡單。

只使用線程私有資源

我們來看這段代碼:
int func() {  int a = 1;  int b = 1;  return a + b;}
這段代碼在前面提到過,無論你在多少個線程中怎么調(diào)用什么時候調(diào)用,func函數(shù)都會確定的返回2,該函數(shù)不依賴任何全局變量,不依賴任何函數(shù)參數(shù),且使用的局部變量都是線程私有資源,這樣的代碼也被稱為無狀態(tài)函數(shù),stateless,很顯然這樣的代碼是線程安全的。

300bd4d0-d597-11ec-bce3-dac502259ad0.png

這樣的代碼請放心大膽的在多線程中使用,不會有任何問題。

有的同學(xué)可能會說,那如果我們還是使用線程私有資源,但是傳入函數(shù)參數(shù)呢?

線程私有資源+函數(shù)參數(shù)

這樣的代碼是線程安全的嗎?自己先想一想這個問題。答案是it depends,也就是要看情況。看什么情況呢?1、按值傳參如果你傳入的參數(shù)的方式是按值傳入,那么沒有問題,代碼依然是線程安全的:
int func(int num) {  num++;  return num;}
這這段代碼無論在多少個線程中調(diào)用怎么調(diào)用什么時候調(diào)用都會正確返回參數(shù)加1后的值。原因很簡單,按值傳入的這些參數(shù)是線程私有資源。

30425014-d597-11ec-bce3-dac502259ad0.png

2、按引用傳參但如果是按引用傳入?yún)?shù),那么情況就不一樣了:
int func(int* num) {  ++(*num);  return *num;}
如果調(diào)用該函數(shù)的線程傳入的參數(shù)是線程私有資源,那么該函數(shù)依然是線程安全的,能正確的返回參數(shù)加1后的值。但如果傳入的參數(shù)是全局變量,就像這樣:
int global_num = 1;
int func(int* num) {  ++(*num);  return *num;}
// 線程1void thread1() {  func(&global_num);}
// 線程2void thread1() {  func(&global_num);}
那此時func函數(shù)將不再是線程安全代碼,因為傳入的參數(shù)指向了全局變量,這個全局變量是所有線程可共享資源,這種情況下如果不改變?nèi)肿兞康氖褂梅绞剑敲磳υ撊肿兞康募?操作必須施加某種秩序,比如加鎖。

305ce74e-d597-11ec-bce3-dac502259ad0.png

有的同學(xué)可能會說如果我傳入的不是全局變量的指針(引用)是不是就不會有問題了?答案依然是it depends,要看情況。即便我們傳入的參數(shù)是在堆上(heap)用malloc或new出來的,依然可能會有問題,為什么?答案很簡單,因為堆上的資源也是所有線程可共享的

30982ade-d597-11ec-bce3-dac502259ad0.png

假如有兩個線程調(diào)用func函數(shù)時傳入的指針(引用)指向了同一個堆上的變量,那么該變量就變成了這兩個線程的共享資源,在這種情況下func函數(shù)依然不是線程安全的。改進也很簡單,那就是每個線程調(diào)用func函數(shù)傳入一個獨屬于該線程的資源地址,這樣各個線程就不會妨礙到對方了,因此,寫出線程安全代碼的一大原則就是能用線程私有的資源就用私有資源,線程之間盡最大可能不去使用共享資源

如果線程不得已要使用全局資源呢?

使用全局資源

使用全局資源就一定不是線程安全代碼嗎?答案還是。。有的同學(xué)可能已經(jīng)猜到了,答案依然是要看情況。如果使用的全局資源只在程序運行時初始化一次,此后所有代碼對其使用都是只讀的,那么沒有問題,就像這樣:
int global_num = 100; //初始化一次,此后沒有其它代碼修改其值
int func() {  return global_num;}
我們看到,即使func函數(shù)使用了全局變量,但該全局變量只在運行前初始化一次,此后的代碼都不會對其進行修改,那么func函數(shù)依然是線程安全的。

30cd1e1a-d597-11ec-bce3-dac502259ad0.png

但是,如果我們簡單修改一下func:
int global_num = 100; 
int func() {  ++global_num;  return global_num;}

這時,func函數(shù)就不再是線程安全的了,對全局變量的修改必須加鎖保護。

線程局部存儲

接下來我們再對上述func函數(shù)簡單修改:
__thread int global_num = 100; 
int func() {  ++global_num;  return global_num;}
我們看到全局變量global_num前加了關(guān)鍵詞__thread修飾,這時,func代碼就是又是線程安全的了。為什么呢?其實在上一篇文章中我們講過,被__thread關(guān)鍵詞修飾過的變量放在了線程私有存儲中,Thread Local Storage,什么意思呢?意思是說這個變量是線程私有的全局變量:
  • global_num是全局變量

  • global_num是線程私有的

310c9d42-d597-11ec-bce3-dac502259ad0.png

各個線程對global_num的修改不會影響到其它線程,因為是線程私有資源,因此func函數(shù)是線程安全的。

說完了局部變量、全局變量、函數(shù)參數(shù),那么接下來就到函數(shù)返回值了。

函數(shù)返回值

這里也有兩種情況,一種是函數(shù)返回的是值;另一種返回對變量的引用。1、返回的是值我們來看這樣一段代碼:
int func() {  int a = 100;  return a;}
毫無疑問,這段代碼是線程安全的,無論我們怎樣調(diào)用該函數(shù)都會返回確定的值100。2、返回的是引用我們把上述代碼簡單的改一改:
int* func() {  static int a = 100;  return &a;}
如果我們在多線程中調(diào)用這樣的函數(shù),那么接下來等著你的可能就是難以調(diào)試的bug以及漫漫的加班長夜。

31234eac-d597-11ec-bce3-dac502259ad0.png

很顯然,這不是線程安全代碼,產(chǎn)生bug的原因也很簡單,你在使用該變量前其值可能已經(jīng)被其它線程修改了。因為該函數(shù)使用了一個靜態(tài)全局變量,只要能拿到該變量的地址那么所有線程都可以修改該變量的值,因為這是線程間的共享資源,不到萬不得已不要寫出上述代碼,除非老板拿刀架在你脖子上。但是,請注意,有一個特例,這種使用方法可以用來實現(xiàn)設(shè)計模式中的單例模式,就像這樣:
class S {public:  static S& getInstance() {    static S instance;    return instance;  }private:  S() {}  // 其它省略}
為什么呢?因為無論我們調(diào)用多少次func函數(shù),static局部變量都只會被初始化一次,這種特性可以很方便的讓我們實現(xiàn)單例模式。

最后讓我們來看下這種情況,那就是如果我們調(diào)用一個非線程安全的函數(shù),那么我們的函數(shù)是線程安全的嗎?

調(diào)用非線程安全代碼

假如一個函數(shù)A調(diào)用另一個函數(shù)B,但B不是線程安全,那么函數(shù)A是線程安全的嗎?答案依然是,要看情況。我們看下這樣一段代碼,這段代碼在之前講解過:
int global_num = 0;
int func() {  ++global_num;  return global_num;}
我們認(rèn)為func函數(shù)是非線程安全的,因為func函數(shù)使用了全局變量并對其進行了修改,但如果我們這樣調(diào)用func函數(shù):
int funcA() {  mutex l;     l.lock();  func();  l.unlock();}
雖然func函數(shù)是非線程安全的,但是我們在調(diào)用該函數(shù)前加了一把鎖進行保護,那么這時funcA函數(shù)就是線程安全的了,其本質(zhì)就是我們用一把鎖間接的保護了全局變量。再看這樣一段代碼:
int func(int *num) {  ++(*num);  return *num;}
一般我們認(rèn)為func函數(shù)是非線程安全的,因為我們不知道傳入的指針是不是指向了一個全局變量,但如果調(diào)用func函數(shù)的代碼是這樣的:
void funcA() {  int a = 100;  func(&a);}
那么這時funcA函數(shù)依然是線程安全的,因為傳入的參數(shù)是線程私有的局部變量,無論多少線程調(diào)用funcA都不會干擾到其它線程。

看了各種情況下的線程安全問題,最后讓我們來總結(jié)一下實現(xiàn)線程安全代碼都有哪些措施。

如何實現(xiàn)線程安全

從上面各種情況的分析來看,實現(xiàn)線程安全無外乎圍繞線程私有資源和線程共享資源這兩點,你需要識別出哪些是線程私有,哪些是共享的,這是核心,然后對癥下藥就可以了。

  • 不使用任何全局資源,只使用線程私有資源,這種通常被稱為無狀態(tài)代碼
  • 線程局部存儲,如果要使用全局資源,是否可以聲明為線程局部存儲,因為這種變量雖然是全局的,但每個線程都有一個屬于自己的副本,對其修改不會影響到其它線程
  • 只讀,如果必須使用全局資源,那么全局資源是否可以是只讀的,多線程使用只讀的全局資源不會有線程安全問題。
  • 原子操作,原子操作是說其在執(zhí)行過程中是不可能被其它線程打斷的,像C++中的std::atomic修飾過的變量,對這類變量的操作無需傳統(tǒng)的加鎖保護,因為C++會確保在變量的修改過程中不會被打斷。我們常說的各種無鎖數(shù)據(jù)結(jié)構(gòu)通常是在這類原子操作的基礎(chǔ)上構(gòu)建的
  • 同步互斥,到這里也就確定了你必須要以某種形式使用全局資源,那么在這種情況下公共場所的秩序必須得到維護,那么怎么維護呢?通過同步或者互斥的方式,這是一大類問題,我們將在《深入理解操作系統(tǒng)》系列文章中詳細闡述這一問題。

總 結(jié)

怎么樣,想寫出線程安全的還是不簡單的吧,如果本文你只能記住一句話的話,那么我希望是這句,這也是本文的核心:實現(xiàn)線程安全無外乎圍繞線程私有資源和線程共享資源來進行,你需要識別出哪些是線程私有,哪些是共享的,然后對癥下藥就可以了。希望本文對大家編寫多線程程序有幫助。

原文標(biāo)題:線程安全代碼到底是怎么編寫的?

文章出處:【微信公眾號:嵌入式ARM】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4887

    瀏覽量

    70264
  • 線程安全
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    2524

原文標(biāo)題:線程安全代碼到底是怎么編寫的?

文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦
    熱點推薦

    不同創(chuàng)建線程安全Set的方式

    方法。 使用ConcurrentHashMap工廠方法構(gòu)造線程安全的HashSet 首先, 我們來看看_ConcurrentHashMap_暴露出來的靜態(tài)方法 -- newKeySet() 。此方法返回一個Set的實例,等同于實現(xiàn)
    的頭像 發(fā)表于 09-25 14:20 ?883次閱讀

    調(diào)用非安全線程的dll的問題

    在調(diào)用非線程安全的dll時,是不是要選擇在UI線程中運行?是不是還必須用不可重入的子VI封裝一下?上述的兩步是不是都要做?這些問題不是很清楚,還請各位大神指點一下
    發(fā)表于 03-14 21:13

    XC32源碼和字符串線程安全

    我正在嘗試我的項目的FrReTOS遷移,我想了解哪些字符串處理函數(shù)是線程安全的,或者不是線程安全的。特別是,我想看看StrudStruts,Strutk,還有一些其他的源
    發(fā)表于 11-26 16:03

    Linux下的線程安全是什么

    Linux下的線程安全原文結(jié)構(gòu)有點亂線程安全:多個執(zhí)行流對臨界資源進行爭搶訪問,而不會造成數(shù)據(jù)二義性和邏輯混亂,成這段代碼的過程是
    發(fā)表于 07-01 13:34

    什么是線程安全?如何去實現(xiàn)線程安全

    什么是線程安全?如何去實現(xiàn)線程安全?互斥實現(xiàn)的技術(shù)是什么?有哪些注意事項?同步
    發(fā)表于 07-23 09:57

    請教大神rtthread中的ringbuff是線程安全的嗎

    最近想用輕量級的ringbuff,請教大神rtthread中的ringbuff是線程安全的嗎?
    發(fā)表于 07-29 10:44

    什么是線程安全

    線程安全的鏈表-隊列-棧,就是多線程同時操作(包括查找、添加、刪除等)鏈表、隊列或棧,無論如何操作,就是多線程同時操作(包括查找、添加、刪除等)鏈表、隊列或棧,無論如何操作,都不會產(chǎn)生
    發(fā)表于 11-17 11:16 ?1次下載

    解決線程安全問題技巧匯總

    線程,有時被稱為輕量級進程,是程序執(zhí)行流的最小單元。一個標(biāo)準(zhǔn)的線程線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統(tǒng)獨立調(diào)度和分派的基本單位
    發(fā)表于 12-01 13:42 ?1625次閱讀

    java的線程安全、單例模式、JVM內(nèi)存結(jié)構(gòu)

    線程安全就是多線程訪問時,采用了加鎖機制,當(dāng)一個線程訪問類的某個數(shù)據(jù)時,進行保護,其他線程不能進行訪問直到該
    發(fā)表于 03-12 10:30 ?0次下載

    什么是線程線程池中線程實現(xiàn)復(fù)用的原理

    一般建議自定義線程工廠,構(gòu)建線程的時候設(shè)置線程的名稱,這樣就在查日志的時候就方便知道是哪個線程執(zhí)行的代碼
    發(fā)表于 01-29 13:44 ?1911次閱讀

    如何理解線程安全

    本次分享線程安全的基礎(chǔ)知識。
    的頭像 發(fā)表于 05-08 15:03 ?1009次閱讀
    如何理解<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?

    什么是線程安全?如何理解線程安全

    在多線程編程中,線程安全是必須要考慮的因素。
    的頭像 發(fā)表于 05-30 14:33 ?2404次閱讀
    什么是<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?如何理解<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?

    線程安全怎么辦

    線程安全一直是多線程開發(fā)中需要注意的地方,可以說,并發(fā)安全保證了所有的數(shù)據(jù)都安全。 1 線程
    的頭像 發(fā)表于 10-10 15:00 ?533次閱讀
    <b class='flag-5'>線程</b><b class='flag-5'>安全</b>怎么辦

    如何知道你的代碼是否線程安全

    的?如何知道你的代碼是否線程安全?要如何訪問數(shù)據(jù)才能保證數(shù)據(jù)的安全? 本篇文章會一一回答你的問題。 1. 線程
    的頭像 發(fā)表于 11-01 11:42 ?965次閱讀
    如何知道你的<b class='flag-5'>代碼</b>是否<b class='flag-5'>線程</b><b class='flag-5'>安全</b>

    redis多線程還能保證線程安全

    Redis是一種使用C語言編寫的高性能鍵值存儲系統(tǒng),它是單線程的,因為使用了多路復(fù)用的方式來處理并發(fā)請求。這樣的實現(xiàn)方式帶來了很好的性能,但同時也引發(fā)了一些線程安全方面的問題。 在Re
    的頭像 發(fā)表于 12-05 10:28 ?2195次閱讀