原文鏈接:http://tutorials.jenkov.com/java-concurrency/same-threading.html
摘要:這是翻譯自一個大概30個小節的關於Java併發編程的入門級教程,原作者Jakob Jenkov,譯者Zhenning Lang,轉載請註明出處,thanks and have a good time here~~~(希望自己不要留坑)
第五節 同一線程(Same-threading)
“同一線程”是這樣一種併發方式:一個單線程系統被擴展成
一個同一線程系統並不是一個單純的單線程系統,因爲他包含多個線程。然而其中的每一個線程都像一個單線程系統一樣。
1 爲什麼還需要單線程
你可能正在疑惑爲何如今竟然還有人設計單線程系統。單線程系統之所以流行是因爲:基於單線程系統的併發模型相比多線程併發模型是非常簡化的。單線程系統不需要在多個線程間共享任何數據。這使得基於單線程的併發可以使用非併發的數據結構,並且充分利用CPU和緩存的結構。
然而不幸的是,單線程系統並不能充分利用現代的多核CPU結構(雙核、四核或更多)。而單線程系統只能利用其中的一個核,如下圖所示:
2 同一線程,單線程的規模化
爲了充分的利用多核,單線程系統可以被規模化來充分利用計算的全部CPU資源。
2.1 每個CPU一個線程
在一個同一線程系統中,通常每個CPU中運行一個任務。如果一個計算機有四個CPU,或者一個CPU是四核的,那麼這個計算機通常用來運行同一線程系統的4個實例(4個單線程系統),如下圖所示
3 無共享狀態
一個同一線程系統和一個多線程系統看起來是很相似的,因爲二者都是在計算機中跑多條線程,但他們之間卻存在着微妙的區別。同一線程系統和多線程系統的區別在於前者不共享狀態,即不同的線程間不在同時擁有共享內存的使用權。如下圖所示,
無共享數據使得同一線程系統中的每個線程表現得像單線程系統一樣。然而,由於同一線程系統可以包含多於一個線程,所以它並非真正的單線程系統。由於缺乏專業術語,我覺得用“同一線程系統”(same-threaded system)比用“具有單線程設計的多線程系統”(multi-threaded system with a single-threaded design)來的更精確一些。同一線程系統這個稱謂即容易說又容易懂。同一線程的基礎定義是在單線程中進行數據處理,並且沒有共享任何共享數據。
4 負載分配
顯而易見,同一線程系統需要在單線程的實例中分擔任務。如果不這樣做,可能會出現一個單獨的實例承擔全部的任務負載,那麼系統將退化爲單線程了。
你的系統設計決定了負載是如何在不同的實例間分配的,下面簡述了其中的幾種設計。
4.1 單線程的微服務
如果你的系統包含了多個微服務,每個微服務可以以單線程被運行,那麼當你在同一臺機器上部署多個單線程的微服務時,每個CPU可以用來運行一個單線程的微服務。
各個微服務之間本質上不共享任何數據,所以微服務系統很適合用同一線程來實現。
4.2 共享數據的服務
如果你的系統有共享數據的需求,或者至少要共享數據庫,那麼你可以“劃破”(shard)數據庫。“劃破”意味着數據被在不同的數據庫之間劃分。一種典型的數據劃分是將有相互關係的數據分配在同一個數據庫中。例如,所有屬於某個“擁有者”的數據被插入同一個數據庫。“劃破”超出了本教程的討論範疇,如果感興趣請搜索相關介紹。1
5 線程間通信
如果同一線程系統中的多個線程需要相互通信,他們通過傳遞消息來完成通信。
利用消息進行通信的過程如下圖所示:
這種消息可以利用隊列(queues)、管道(pipes)、嵌套字(unix sockets,tcp sockets)等技術來實現,這需要根據具體系統選擇具體的消息實現機制。
6 一種極簡的併發模型
同一線程模型實現的併發系統中,每個部分在其自己的線程中運行就好像單線程一樣。無共享狀態意味着這種模型是一種極簡的模型 - 編程者不再需要擔心共享數據和這導致的一系列問題。
7 例子
對於單線程系統、多線程系統和同一線程系統,爲了讓讀者可以更容易的構建起相關概念,請看下面的例子:
單線程系統:
共享數據的多線程系統:
同一線程系統(數據分離,通過消息機制通信)
譯者的一點補充:
(1) 首先是在翻譯過程中忽然想到Java的多線程真的可以運行在多個CPU中嗎?
因爲之前接觸過OpenMP和MPI的入門,記得跨CPU的通信好像不是那麼容易的事情,所以就會懷疑Java的多線程實際是在一個CPU內實現的。於是搜了一下,搜到這個帖子:
http://blog.csdn.net/ziwen00/article/details/38097297 。
但心動不如行動,我決定自己試一試(本人筆記本四核、4G內存),測試代碼如下:
public class Test {
public static void main(String[] args) {
int threadNum = 1; // 2 3 ...
for(int i = 0; i < threadNum; i++){
System.out.println(i);
new Thread(new myThread(), "Thread" + i).start();
}
}
}
class myThread implements Runnable{
@Override
public void run() {
while(true);
}
}
通過改變程序中的threadNum,測試結果如下,
線程數 | CPU使用率 |
---|---|
1 | 33% |
2 | 56% |
3 | 80% |
4 | 100% |
又想到了某一節中作者說開好多線程的事(作者在介紹多線程的缺點時說可以嘗試開100個線程看一看系統內存的負荷有多少),就蛋疼的試了。。。
public class Test {
public static void main(String[] args) {
int threadNum = 1; // 2, 3, ...
for(int i = 0; i < threadNum; i++){
System.out.println(i);
new Thread(new myThread(), "Thread" + i).start();
}
}
}
class myThread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
其結果如下
線程數 | 運行前(G) | 運行後(G) | 使用內存 |
---|---|---|---|
1 | 2.4 | 2.4 | 推算約爲0.1M |
10 | 2.4 | 2.4 | 推算約爲1M |
100 | 2.4 | 2.4 | 推算約爲10M |
1000 | 2.4 | 2.5 | 0.1G |
10000 | 2.4 | 3.2 | 0.8G |
100000 | 2.4 | 大於4(實際是系統卡死了…) | 大於1.6G,推算約爲 8G |
實驗結果僅做參考
(2) 這一節介紹的內容實際應用還是很多的。本人研究生階段做過一個大規模的仿真程序,然後並行化進行提速,用的就是這節介紹的同一線程模型。如果可以利用好問題特性,這種模型可以說無論用什麼語言都是非常容易實現的。
尾註:
- 關於sharding的內容可以參考博客
http://blog.csdn.net/bluishglc/article/details/6161475/
以及百科
http://baike.baidu.com/link?url=tNIxCQAgDkC4V0dOQHmP_ZGWs5ruDAuWWQfFtOVHyvMB0DMzvG1yagyT9pJASroz0o0nTlJX_NOmsVWTxsi4zq,簡而言之sharding就是一種分割數據庫的技術 ↩