一、多線程
多線程不管是在前面的文章分析中還是在網上還是大書籍上,學習C/C++多線程編程是無法繞過的,即使使用別人封裝好的框架,包括使用STL的一些庫,如果僅僅是簡單的應用,可能也就過去了。不過,稍微復雜的一些應用,其實就需要對多線程的應用有深層次的理解。
尤其在使用一些封裝庫時,特別要注意庫的一些注意事項,往往這些注意點是應用BUG的重要原因。比如std::async中std::future返回值如果控制不好,異步就可能變成了同步。所以說多線程要想寫好,不但要會用一些庫一些API,更要懂得這些庫的底層原理,直到這個庫調用的不同的平臺,封裝的是哪個OS的系統函數。如果再展開到并發和并行,更得需要了解相關OS的相關進程線程的調度知識等等。這些都是寫多線程的開發者需要一步步學習的目標。不過在這里,只就時論事,只講多線程,與OS相關的知識,用到就展開分析一下。
這個系列更側重于線程的應用,而非基礎知識的普及,所以如果在示例的一些源碼代碼中不清楚的基礎的用法,需要去查一下前面的多線程的基礎知識的文章或者自己行在網上查找一些相關知識。
二、線程的運用
多線程的應用其實是一個非常重要且普遍的應用。協程的編程目前看來應該是被排除到了更高的應用層上,也就是說,想用好協程,更多的是純上層應用了。線程被遷移到了一個中間層的庫應用。說得再深入一些,協程的普及,可能會讓開發者寫并發變得簡單但同樣大幅降低了技術門檻,這意味著什么,以前不明白的,這兩年應該都懂了。
多線程的應用一般會和異步IO共同使用,有的人在網上說多線程和異步有什么不同,這就說汽車和鋼鐵有什么不同一樣,這本來就是兩回事。說多了,扯回來。
多線程的應用非常廣泛,圖像處理、數據讀寫、IO通信和時間控制等等,都可以用到。但正如已經分析過的一樣,用的方法不一樣,那么產生的結果也不一樣。單純的多線程讀寫操作不涉及到數據互斥沒有什么難度。舉個簡單的例子,寫十個線程,讀十個不同的文件,這和寫普通的非線程代碼沒有本質不同,可能只是套上了十個線程的殼子。但是如果這十個線程需要同時把讀到數據寫到一個緩沖區內,根據不同情況來覆蓋或者追加相關數據,這就需要謹慎的處理了。否則數據很可能不是丟失就是多存儲了。
另外,如果只有五個線程來處理十個文件呢?如果這十個文件大小不一,有的非常大,有的非常小?那么小文件讀取完成后,這個線程不就空閑了么?多線程不還是單線程么。再細分一下,大文件可不可以切成段,在別的線程讀完成文件后去讀這些分段的大文件來加速讀取的速度呢?這都是多線程需要解決的現實問題。
很多問題,在學習多線程編程時,是很難想到的或者說遇到實際問題不知道怎么下手,這其實就是學無法致用的一個典型的表現。在這個系列中,就把這些多線程的用法與實際的情況結合起來,進行一一的分析。
三、例程
做為簡單的開始,本篇不做多么復雜的例程,只是把剛剛提到std::async遇到的同步問題展現一下以及相關的處理辦法:
#include <iostream>
#include <future>
#include <chrono>int getData(int x) {std::cout << "async thread run start!"<<std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(6000));std::cout << "async thread run end!" << std::endl;return x;
}int main()
{std::async(std::launch::async, getData, 100);std::cout << "go to ..."<<std::endl;return 0;
}
運行結果:
async thread run start!
async thread run end!
go to ...
你會發現運行的結果并不是期房的兩個線程并行執行,而執行完async中的線程,才回頭執行主線程的函數。什么原因呢,在std::async中有下面的話:
“If the std::future obtained from std::async is not moved from or bound to a reference, the destructor of the std::future will block at the end of the full expression until the asynchronous operation completes, essentially making code such as the following synchronous”
在std::future的析構函數中:
“these actions will not block for the shared state to become ready, except that they may block if all of the following are true:
the shared state was created by a call to std::async,
the shared state is not yet ready, and
the current object was the last reference to the shared state.”
在下面還有一句話,意思是說只在運行策略為“ std::launch::async ”才會產生這種情況。
所以通過上述兩個說明就明白了,在std::async中,std::future的臨時變量會在沒有被移動或者引用情況下一直阻塞到異步計算完成,所以這句話也就給了解決方法:
auto d = std::async(std::launch::async, getData, 100);
只需增加上面的返回值處理即可,延長一下std::future的生命周期至期望的位置即可。用別人的東西,就得服別人的套路,一個不小心,就會吃虧上當。不過話又說回來,想省事,吃點虧也應該,對吧。
四、總結
從簡單開始,不忘初心,朝著實際應用不斷的前進。多線程編程屬于那種入門容易,寫好難,精通更難的一種編程技術,真正的生產上,大牛們被憋住當場打臉的情況也不少見。總之一句話,多看書,多實踐,多應用。小心寫代碼,防御性編程,少引入BUG比如事后調試定位要更容易做到一些。