1. 程式人生 > >(三)執行緒傳參詳解,detach()大坑,成員函式做執行緒函式

(三)執行緒傳參詳解,detach()大坑,成員函式做執行緒函式

陷阱1:子執行緒傳遞引數避免使用指標

先來看一段程式碼: 在這裡插入圖片描述 程式碼乍一看好像沒有什麼問題,執行有時也能成功,但是卻隱含了一個很大的bug。 通過除錯:變數i的地址:0x0076e384 {1},變數mvar和mvary的地址:0x003bfe40 {1},可以看出雖然myprint函式形參i是添加了引用,但是子執行緒中變數i的地址和主執行緒中的地址不一樣,所以子執行緒的第一個引數mvar是安全的,因為即使主執行緒結束mvar的記憶體被釋放, 子執行緒中的遍歷i依然存在。 再看子執行緒的第二個引數,通過指標傳遞,mybuf的首地址:0x003bfe18,指標pmybuf的地址:0x003bfe18,可以看出主執行緒中的mybuf和子執行緒中pmybuf變數使用的是同一塊記憶體,所以當主執行緒執行完後有可能已經釋放了這塊地址,導致子執行緒引用非法記憶體。 所以根據以上除錯得出一個結論:執行緒傳參使用detach()函式一定不能使用指標!!!

陷阱二:

但是要修改上述程式碼存在的問題,是不是保證子執行緒和主執行緒中的引數記憶體地址不一樣是不是就可以了呢???先看看修改成如下程式碼: 在這裡插入圖片描述 現在已經避免了陷阱一中子執行緒和主執行緒變數記憶體地址一樣的情況,但是現在依然還存在一個問題:mybuf到底是什麼時候隱式轉換成string的臨時物件的呢?如果主執行緒已經執行完成後再轉,mybuf記憶體先被釋放,子執行緒就會出現無法預料的問題。 事實是,如果直接傳入引數而不先隱式轉換成臨時物件,有可能發生記憶體在執行子執行緒前被釋放的情況,依然是不安全的。 進一步修改,在16行使用string函式先隱式轉換成臨時物件: 在這裡插入圖片描述 這樣就不會有bug了,這樣就得出了一個結論:只要用臨時構造的物件傳遞給子執行緒,那麼一定能在主執行緒執行完畢前把子執行緒的引數構造出來。

再來看一個類似的案例:

在這裡插入圖片描述 第24行程式碼,如果直接寫成:

thread mytobj(myprint,mvar, mvar);

就有可能出現主執行緒結束後先釋放了mvar,導致子執行緒引用非法記憶體的情況。但是如果構造臨時變數,就是安全的。輸出如下: 在這裡插入圖片描述 需要注意的是,這時候呼叫了類的建構函式和拷貝建構函式,如果第15行程式碼不使用引用:

void myprint(const int i, const A mybuf)

這將呼叫兩個拷貝建構函式(如下圖),造成浪費,所以使用類作為傳遞引數時,儘量使用引用傳遞,節約資源。 在這裡插入圖片描述

總結:

1. 子執行緒引數通過構造臨時物件的方式來進行傳遞,構造臨時物件主要有兩種情況 (1)傳遞int這種簡單型別引數,建議使用值傳遞,不要使用引用,防止節外生枝。 (2)如果傳遞類物件,一律在建立執行緒的地方就通過隱式轉換構建臨時物件,然後在函式形參處使用引用(節約資源)。 2. 建議不使用detach(),只使用join(),這樣就不存在區域性變數失效導致執行緒對記憶體非法引用的問題。