Golang如何并發

jopen 10年前發布 | 14K 次閱讀 Google Go/Golang開發 Golang

Concurrency

Concurrency is a property of systems in which several computations are executing simultaneously, and potentially interacting with each other,維基百科上這樣定義Concurrency。多線程在同一個核內分時執行或者多核下多進程同時執行都可以被稱為Concurrency。并發 的數學模型已經發展的非常成熟,諸如我們常用的多進程,以及erlang用的Actor模型,golang用的CSP模型等。

Threads, Processes and Green Threads

在linux中,進程和線程的都由task_struct對象來表示,區別在于signal handler,以及線程共享了虛擬內存空間等,盡管這些區別可能會讓線程在創建和context switch的時候更加輕松些,但其實看起來沒有明顯的區別。

當然進程并不只是task_struct對象,它是可執行代碼和所操作數據的集合,是一個動態的實體,隨著可執行代碼的執行而動態變化。進程同時包 含了程序計數器,CPU內的各個寄存器以及存放臨時變量的程序棧等。所以我們大概可以想象出進程在進行context switch時要進行的操作。

kernel負責對進程(線程)進行調度,系統會把不同的進程運行在不同的CPU上來提升CPU的利用率。當一個進程阻塞時,CPU會被調度執行其 他的進程。調度的目的是提高系統的在單位時間內執行的任務吞吐量,降低任務從可執行到執行結束的時間,使進程占用的資源更加合理,系統對于進程和線程的調 度是無差別的。

green threads可以理解是用戶態線程,它運行在用戶空間中。也就是我們在程序層面實現了類似操作系統線程這樣的概念,通常這個實現邏輯會簡單許多。

green threads可以帶來什么?最顯而易見的是:在不支持線程的操作系統上,我們依舊可以在語言層面上實現green threads。其次操作系統提供的線程是一個大而全的對象,而對于用戶程序來說,諸多功能是冗余的,因此green threads的構造可以更簡單,調度策略也更加簡單。goroutine可以理解為是一種green threads,它建立在系統線程之上,所以go程序可以并行執行成千上萬個goroutine。

Goroutine

green threads的實現通常有三種模型:多個green threads運行在同一個kernel thread上,優點是context switch的速度快,但是只能運行在一個核上;一個green thread對應一個kernel thread,優點是可以利用多核,但是由于綁定關系context swtich會更耗時。

goroutine使用了第三種模型,也就是多個green threads運行在多個kernel threads上,既可以快速的進行context switch又可以很好的利用多核。顯然缺點是調度策略會因此變得非常復雜。goroutine的實現使用work stealing算法,定義了MPG三個角色,分別代表kernel threads,processor和goroutine。

 Golang如何并發

P也可以理解為context,我們設置的GOMAXPROCS就是指的P的數量。P負責完成對G和M的調度。P維護了一個deque來存放可執行 的G,進行調度時切換當前執行的G,從deque頂部取出下一個G繼續執行。當G進行syscall阻塞時,不僅需要切換G,M也需要進行切換來保證P能 夠繼續執行后面的G,當阻塞的G-M對就緒時會被重新調度執行。同時當P維護的所有的G執行結束后,會從別的P的deque的steal一半的G放入自己 的deque中執行,這也就是為什么叫做work steal算法。

Non-Blocking I/O

Go提供了同步阻塞的IO模型,但Go底層并不沒有使用kernel提供的同步阻塞I/O模型。green threads通常在實現的時候會避免使用同步阻塞的syscall,原因在于當kernel threads阻塞時,需要創建新的線程來保證green threads能夠繼續執行,代價非常高。所以Go在底層使用非阻塞I/O的模型來避免M阻塞。

當goroutine嘗試進行I/O操作而IO未就緒時,syscall返回error,當前執行的G設置為阻塞,而M可以被調度繼續執行其他的 G,這樣就不需要再去創建新的M。當然只是Non-Blocking I/O還不夠,Go實現了netpoller來進行I/O的多路復用,在linux下通過epoll實現,epoll提供了同步阻塞的多路復用I/O模 型。當G阻塞到I/O時,將fd注冊到epoll實例中進行epoll_wait,就緒的fd回調通知給阻塞的G,G重新設置為就緒狀態等待調度繼續執 行,這種實現使得Go在進行高并發的網絡通信時變得非常強大,相比于php-fpm的多進程模型,Go Http Server使用很少的線程運行非常多的goroutine,而盡可能的讓每一個線程都忙碌起來,這樣提高了CPU的利用率,減少了系統進行 context switch的開銷,從而hold住更大的并發量級。

Done




參考:
https://morsmachine.dk
http://www.tldp.org/LDP/tlk/kernel/processes.html http://man7.org/linux/man-pages/man7/epoll.7.html http://man7.org/tlpi/download/TLPI-04-FileIOTheUniversalIO_Model.pdf

來自:http://blog.panic.so/share/2015/09/17/Golang如何并發/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!