postgres 支持的线程数_为什么 Java 坚持多线程不选择协程?
先說結論:協程是非常值得學習的概念,它是多任務編程的未來。但是Java全力推進這個事情的動力并不大。
先返回到問題的本源。當我們希望引入協程,我們想解決什么問題。我想不外乎下面幾點:
節省資源,輕量,具體就是:
節省內存,每個線程需要分配一段棧內存,以及內核里的一些資源
節省分配線程的開銷(創建和銷毀線程要各做一次syscall)
節省大量線程切換帶來的開銷
與NIO配合實現非阻塞的編程,提高系統的吞吐
使用起來更加舒服順暢(async+await,跑起來是異步的,但寫起來感覺上是同步的)
我們分開來講下。
先說內存。拿Java Web編程舉例子,一個tomcat上的woker線程池的最大線程數一般會配置為50~500之間(目前springboot的默認值給的200)。也就是說同一時刻可以接受的請求最多也就是這么多。如果超過了最大值,請求直接打失敗拒絕處理。假如每個線程給128KB,500個線程放一起的內存占用量大概是60+MB。如果真的有瓶頸,也許CPU,IO,帶寬,DB的CPU等會有瓶頸,但這點內存量的增幅對于動輒數個GB的Java運行時進程來說似乎并不是什么大問題。
上面的討論簡化了RSS和VM的區別。實際上一個線程啟動后只會在虛擬地址上占位置那么多的內存。除非實際用上,是不會真的消耗物理內存的。換一個場景,比如IM服務器,需要同時處理大量空閑的鏈接(可能要幾十萬,上百萬)。這時候用connection per thread就很不劃算了。但是可以直接改用netty去處理這類問題。你可以理解為NIO + woker thread大致就是一套“協程”,只不過沒有實現在語法層面,寫起來不優雅而已。問題是,你的場景真的處理了并發幾十萬,上百萬的連接嗎?
再說創建/銷毀線程的開銷。這個問題在Java里通過線程池得到了很好的解決。你會發現即便你用vert.x或者kotlin的協程,歸根到底也是要靠線程池工作的。goroutine相當于設置一個全局的“線程池”,GOMAXPROCS就是線程池的最大數量;而Java可以自由設置多個不同的線程池(比如處理請求一套,異步任務另外一套等)。kotlin利用這個機制來構建多個不同的協程scope。這看起來似乎會更靈活一點。
然后是線程的切換開銷。線程的切換實際上只會發生在那些“活躍”的線程上。對于類似于Web的場景,大量的線程實際上因為IO(發請求/讀DB)而掛起,根本不會參與OS的線程切換?,F實當中一個最大200線程的服務器可能同一時刻的“活躍線程”總數只有數十而已。其開銷沒有想象的那么大。為了避免過大的線程切換開銷,真正要防范的是同時有大量“活躍線程”。這個事情我自己上學的時候干過,當時是寫了一個網絡模擬器。每一個節點,每一個鏈路都由一個線程實現。模擬跑起來后,同時的活躍線程上千。當時整個機器瞬間卡死,直到kill掉這個程序。
此外說說與NIO的配合。在Java這個生態里Java NIO/Netty/Vert.X/rxJava/Akka可以任意選擇。一般來講,Netty可以解決絕大部分因為IO的等待造成資源浪費的問題。Vert.X/rxJava??梢宰尦绦驅懙母印皟炑拧币稽c(見仁見智)。Akka就是Java世界里對“原教旨OO“的實現,很有特色。的確,用NIO + completedFuture/handler/lambda不如async+await寫起來舒服,但起碼是可以干活的。
如果真的要較真Java的NIO用于業務的問題,其核心痛點應該是JDBC。這是個誕生了幾十年的,必須使用Blocking IO的DB交互協議。其上承載了Java龐大的生態和業務邏輯。Java要改自己的編程方式,必須得重新設計和實現JDBC,就像https://github.com/vert-x3/vertx-mysql-postgresql-client 那樣做。問題是,社區里這種“異步JDBC”還沒有支持oracle、sql server等傳統DB。對mysql和postgres的支持還需要繼續趟坑~
如果認真閱讀上面這些需要“協程”解決的問題,就會發現基本上都可以以各種方式解決。覺得線程耗資源,可以控制線程總數,可以減少線程stack的大小,可以用線程池配置max和min idle等等。想要go的channel,可以上disruptor??梢哉f,Java這個生態里盡管沒有“協程”這個第一級別的概念,但是要解決問題的工具并不缺。
Java僅僅是沒有解決”協程“在Java中的定義,以及“寫得優雅“這個問題。從工程角度,“寫得優雅”的優勢并沒有很多追新的人想象的那么關鍵。C#也并非因為有了async await就搶了Java的市場分毫。而反過來,如果java社區全力推進這個事情,Java歷史上的生態的積累卻因為協程的出現而進行大換血。想像一下如果沒有thread,也沒有ThreadLocal,@Transactional不起作用了,又沒有等價的工具,是不是很郁悶?這么看來怎么著都不是個劃算的事情。我想Oracle對此并不會有太大興趣。OpenJDK的loom能不能成,如果真的release多少Java程序員愿意使用,師母已呆。據我所知在9012年的今天,還有大量的Java6程序員。
其他新的語言歷史包袱少,比較容易重新思考“什么是現代的multi-task編程的方式“這個大主題。kotlin的協程、go的goroutine、javascript的async await、python的asyncio、swift的GCD都給了各自的答案。如果真的想入坑Java這個體系的“協程”,就從kotlin開始吧,畢竟可以混合編程。
最后說一句,多線程容易出bug主要因為:
“搶占“式的線程切換 —— 你無法確定兩個線程訪問數據的順序,一切都很隨機
“同步“不可組裝 —— 同步的代碼組裝起來也不同步,必須加個更大的同步塊
協程能不能避免容易出bug的缺陷,主要看能不能避免上面兩個問題。
如果協程底層用的還是線程池,兩個協程還是通過共享內存通訊,那么多線程該出什么bug,多協程照樣出。javascript里不出這種bug是因為其用戶線程就一個,不會出現線程切換,也不用同步;go是建議用channel做goroutine的通訊。如果go routine不用channel,而是用共享變量,并且沒有用Sync包控制一下,還是會出bug。
編輯整理丨羅西亞
總結
以上是生活随笔為你收集整理的postgres 支持的线程数_为什么 Java 坚持多线程不选择协程?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 极米Z1出来都要半年了,极米Z2 好久出
- 下一篇: 精子成活率低的原因