摘要:所以系統(tǒng)的吞吐能力取決于每個(gè)線程的操作耗時(shí)。另外過多的線程,也會(huì)帶來更多的開銷。其代表派是以及里的新秀。類似線程也有自己的棧。線程中斷的條件只有兩個(gè),一個(gè)是拋異常,另外一個(gè)就是。
什么是協(xié)程(coroutine)
這東西其實(shí)有很多名詞,比如有的人喜歡稱為纖程(Fiber),或者綠色線程(GreenThread)。其實(shí)最直觀的解釋可以定義為線程的線程。有點(diǎn)拗口,但本質(zhì)上就是這樣。
我們先回憶一下線程的定義,操作系統(tǒng)產(chǎn)生一個(gè)進(jìn)程,進(jìn)程再產(chǎn)生若干個(gè)線程并行的處理邏輯,線程的切換由操作系統(tǒng)負(fù)責(zé)調(diào)度。傳統(tǒng)語言C++ Java等線程其實(shí)與操作系統(tǒng)線程是1:1的關(guān)系,每個(gè)線程都有自己的Stack, Java在64位系統(tǒng)默認(rèn)Stack大小是1024KB,所以指望一個(gè)進(jìn)程開啟上萬個(gè)線程是不現(xiàn)實(shí)的。但是實(shí)際上我們也不會(huì)這么干,因?yàn)槠疬@么多線程并不能充分的利用CPU,大部分線程處于等待狀態(tài),CPU也沒有這么核讓線程使用。所以一般線程數(shù)目都是CPU的核數(shù)。
傳統(tǒng)的J2EE系統(tǒng)都是基于每個(gè)請(qǐng)求占用一個(gè)線程去完成完整的業(yè)務(wù)邏輯,(包括事務(wù))。所以系統(tǒng)的吞吐能力取決于每個(gè)線程的操作耗時(shí)。如果遇到很耗時(shí)的I/O行為,則整個(gè)系統(tǒng)的吞吐立刻下降,比如JDBC是同步阻塞的,這也是為什么很多人都說數(shù)據(jù)庫(kù)是瓶頸的原因。這里的耗時(shí)其實(shí)是讓CPU一直在等待I/O返回,說白了線程根本沒有利用CPU去做運(yùn)算,而是處于空轉(zhuǎn)狀態(tài)。暴殄天物啊。另外過多的線程,也會(huì)帶來更多的ContextSwitch開銷。
Java的JDK里有封裝很好的ThreadPool,可以用來管理大量的線程生命周期,但是本質(zhì)上還是不能很好的解決線程數(shù)量的問題,以及線程空轉(zhuǎn)占用CPU資源的問題。
先階段行業(yè)里的比較流行的解決方案之一就是單線程加上異步回調(diào)。其代表派是node.js以及Java里的新秀Vert.x。他們的核心思想是一樣的,遇到需要進(jìn)行I/O操作的地方,就直接讓出CPU資源,然后注冊(cè)一個(gè)回調(diào)函數(shù),其他邏輯則繼續(xù)往下走,I/O結(jié)束后帶著結(jié)果向事件隊(duì)列里插入執(zhí)行結(jié)果,然后由事件調(diào)度器調(diào)度回調(diào)函數(shù),傳入結(jié)果。這時(shí)候執(zhí)行的地方可能就不是你原來的代碼區(qū)塊了,具體表現(xiàn)在代碼層面上,你會(huì)發(fā)現(xiàn)你的局部變量全部丟失,畢竟相關(guān)的棧已經(jīng)被覆蓋了,所以為了保存之前的棧上數(shù)據(jù),你要么選擇帶著一起放入回調(diào)函數(shù)里,要么就不停的嵌套,從而引起反人類的Callback hell.
因此相關(guān)的Promise,CompletableFuture等技術(shù)都是為解決相關(guān)的問題而產(chǎn)生的。但是本質(zhì)上還是不能解決業(yè)務(wù)邏輯的割裂。
說了這么多,終于可以提一下協(xié)程了,協(xié)程的本質(zhì)上其實(shí)還是和上面的方法一樣,只不過他的核心點(diǎn)在于調(diào)度那塊由他來負(fù)責(zé)解決,遇到阻塞操作,立刻yield掉,并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再找一個(gè)線程恢復(fù)棧并把阻塞的結(jié)果放到這個(gè)線程上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個(gè)流程可以稱為coroutine,而跑在由coroutine負(fù)責(zé)調(diào)度的線程稱為Fiber。比如Golang里的 go關(guān)鍵字其實(shí)就是負(fù)責(zé)開啟一個(gè)Fiber,讓func邏輯跑在上面。而這一切都是發(fā)生的用戶態(tài)上,沒有發(fā)生在內(nèi)核態(tài)上,也就是說沒有ContextSwitch上的開銷。
既然我們的標(biāo)題叫Java里的協(xié)程,自然我們會(huì)討論JVM上的實(shí)現(xiàn),JVM上早期有kilim以及現(xiàn)在比較成熟的Quasar。而本文章會(huì)全部基于Quasar,因?yàn)?b>kilim已經(jīng)很久不更新了。
簡(jiǎn)單的例子,用Java寫出Golang的味道上面已經(jīng)說明了什么是Fiber,什么是coroutine。這里嘗試通過Quasar來實(shí)現(xiàn)類似于golang的coroutine以及channel。這里假設(shè)各位已經(jīng)大致了解golang。
為了對(duì)比,這里先用golang實(shí)現(xiàn)一個(gè)對(duì)于10以內(nèi)自然數(shù)分別求平方的例子,當(dāng)然了可以直接單線程for循環(huán)就完事了,但是為了凸顯coroutine的高逼格,我們還是要稍微復(fù)雜化一點(diǎn)的。
func counter(out chan<- int) { for x := 0; x < 10; x++ { out <- x } close(out) } func squarer(out chan<- int, in <-chan int) { for v := range in { out <- v * v } close(out) } func printer(in <-chan int) { for v := range in { fmt.Println(v) } } func main() { //定義兩個(gè)int類型的channel naturals := make(chan int) squares := make(chan int) //產(chǎn)生兩個(gè)Fiber,用go關(guān)鍵字 go counter(naturals) go squarer(squares, naturals) //獲取計(jì)算結(jié)果 printer(squares) }
上面的例子,有點(diǎn)類似生產(chǎn)消費(fèi)者模式,通過channel兩解耦兩邊的數(shù)據(jù)共享。大家可以將channel理解為Java里的SynchronousQueue。那傳統(tǒng)的基于線程模型的Java實(shí)現(xiàn)方式,想必大家都知道怎么做,這里就不啰嗦了,我直接上Quasar版的,幾乎可以原封不動(dòng)的copy golang的代碼。
public class Example { private static void printer(Channelin) throws SuspendExecution, InterruptedException { Integer v; while ((v = in.receive()) != null) { System.out.println(v); } } public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution { //定義兩個(gè)Channel Channel naturals = Channels.newChannel(-1); Channel squares = Channels.newChannel(-1); //運(yùn)行兩個(gè)Fiber實(shí)現(xiàn). new Fiber(() -> { for (int i = 0; i < 10; i++) naturals.send(i); naturals.close(); }).start(); new Fiber(() -> { Integer v; while ((v = naturals.receive()) != null) squares.send(v * v); squares.close(); }).start(); printer(squares); } }
看起來Java似乎要啰嗦一點(diǎn),沒辦法這是Java的風(fēng)格,而且畢竟不是語言上支持coroutine,是通過第三方的庫(kù)。到后面我會(huì)考慮用其他JVM上的語言去實(shí)現(xiàn),這樣會(huì)顯得更精簡(jiǎn)一點(diǎn)。
說到這里各位肯定對(duì)Fiber很好奇了。也許你會(huì)表示懷疑Fiber是不是如上面所描述的那樣,下面我們嘗試用Quasar建立一百萬個(gè)Fiber,看看內(nèi)存占用多少,我先嘗試了創(chuàng)建百萬個(gè)Thread。
for (int i = 0; i < 1_000_000; i++) { new Thread(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); }
很不幸,直接報(bào)Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread,這是情理之中的。下面是通過Quasar建立百萬個(gè)Fiber
public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution { int FiberNumber = 1_000_000; CountDownLatch latch = new CountDownLatch(1); AtomicInteger counter = new AtomicInteger(0); for (int i = 0; i < FiberNumber; i++) { new Fiber(() -> { counter.incrementAndGet(); if (counter.get() == FiberNumber) { System.out.println("done"); } Strand.sleep(1000000); }).start(); } latch.await(); }
我這里加了latch,阻止程序跑完就關(guān)閉,Strand.sleep其實(shí)跟Thread.sleep一樣,只是這里針對(duì)的是Fiber
最終控制臺(tái)是可以輸出done的,說明程序已經(jīng)創(chuàng)建了百萬個(gè)Fiber,設(shè)置Sleep是為了讓Fiber一直運(yùn)行,從而方便計(jì)算內(nèi)存占用。官方宣稱一個(gè)空閑的Fiber大約占用400Byte,那這里應(yīng)該是占用400MB堆內(nèi)存,但是這里通過jmap -heap pid顯示大約占用了1000MB,也就是說一個(gè)Fiber占用1KB。
Quasar是怎么實(shí)現(xiàn)Fiber的其實(shí)Quasar實(shí)現(xiàn)的coroutine的方式與Golang很像,只不過一個(gè)是框架級(jí)別實(shí)現(xiàn),一個(gè)是語言內(nèi)置機(jī)制而已。
如果你熟悉了Golang的調(diào)度機(jī)制,那理解Quasar的調(diào)度機(jī)制就會(huì)簡(jiǎn)單很多,因?yàn)閮烧呤遣畈欢嗟摹?/p>
Quasar里的Fiber其實(shí)是一個(gè)continuation,他可以被Quasar定義的scheduler調(diào)度,一個(gè)continuation記錄著運(yùn)行實(shí)例的狀態(tài),而且會(huì)被隨時(shí)中斷,并且也會(huì)隨后在他被中斷的地方恢復(fù)。Quasar其實(shí)是通過修改bytecode來達(dá)到這個(gè)目的,所以運(yùn)行Quasar程序的時(shí)候,你需要先通過java-agent在運(yùn)行時(shí)修改你的代碼,當(dāng)然也可以在編譯期間這么干。golang的內(nèi)置了自己的調(diào)度器,Quasar則默認(rèn)使用ForkJoinPool這個(gè)JDK7以后才有的,具有work-stealing功能的線程池來當(dāng)調(diào)度器。work-stealing非常重要,因?yàn)槟悴磺宄膫€(gè)Fiber會(huì)先執(zhí)行完,而work-stealing可以動(dòng)態(tài)的從其他的等等隊(duì)列偷一個(gè)context過來,這樣可以最大化使用CPU資源。
那這里你會(huì)問了,Quasar怎么知道修改哪些字節(jié)碼呢,其實(shí)也很簡(jiǎn)單,Quasar會(huì)通過java-agent在運(yùn)行時(shí)掃描哪些方法是可以中斷的,同時(shí)會(huì)在方法被調(diào)用前和調(diào)度后的方法內(nèi)插入一些continuation邏輯,如果你在方法上定義了@Suspendable注解,那Quasar會(huì)對(duì)調(diào)用該注解的方法做類似下面的事情。
這里假設(shè)你在方法f上定義了@Suspendable,同時(shí)去調(diào)用了有同樣注解的方法g,那么所有調(diào)用f的方法會(huì)插入一些字節(jié)碼,這些字節(jié)碼的邏輯就是記錄當(dāng)前Fiber棧上的狀態(tài),以便在未來可以動(dòng)態(tài)的恢復(fù)。(Fiber類似線程也有自己的棧)。在suspendable方法鏈內(nèi)Fiber的父類會(huì)調(diào)用Fiber.park,這樣會(huì)拋出SuspendExecution異常,從而來停止線程的運(yùn)行,好讓Quasar的調(diào)度器執(zhí)行調(diào)度。這里的SuspendExecution會(huì)被Fiber自己捕獲,業(yè)務(wù)層面上不應(yīng)該捕獲到。如果Fiber被喚醒了(調(diào)度器層面會(huì)去調(diào)用Fiber.unpark),那么f會(huì)在被中斷的地方重新被調(diào)用(這里Fiber會(huì)知道自己在哪里被中斷),同時(shí)會(huì)把g的調(diào)用結(jié)果(g會(huì)return結(jié)果)插入到f的恢復(fù)點(diǎn),這樣看上去就好像g的return是f的local variables了,從而避免了callback嵌套。
上面啰嗦了一大堆,其實(shí)簡(jiǎn)單點(diǎn)講就是,想辦法讓運(yùn)行中的線程棧停下來,好讓Quasar的調(diào)度器介入。JVM線程中斷的條件只有兩個(gè),一個(gè)是拋異常,另外一個(gè)就是return。這里Quasar就是通過拋異常的方式來達(dá)到的,所以你會(huì)看到我上面的代碼會(huì)拋出SuspendExecution。但是如果你真捕獲到這個(gè)異常,那就說明有問題了,所以一般會(huì)這么寫。
@Suspendable public int f() { try { // do some stuff return g() * 2; } catch(SuspendExecution s) { //這里不應(yīng)該捕獲到異常. throw new AssertionError(s); } }與Golang性能對(duì)比
在github上無意中發(fā)現(xiàn)一個(gè)有趣的benchmark,大致是測(cè)試各種語言在生成百萬actor/Fiber的開銷skynet。
大致的邏輯是先生成10個(gè)Fiber,每個(gè)Fiber再生成10個(gè)Fiber,直到生成1百萬個(gè)Fiber,然后每個(gè)Fiber做加法累積計(jì)算,并把結(jié)果發(fā)到channel里,這樣一直遞歸到根Fiber。后將最終結(jié)果發(fā)到channel。如果邏輯沒有錯(cuò)的話結(jié)果應(yīng)該是499999500000。我們搞個(gè)Quasar版的,來測(cè)試一下性能。
所有的測(cè)試都是基于我的Macbook Pro Retina 2013later。Quasar-0.7.5:JDK8,JDK 1.8.0_91,Golang 1.6
public class Skynet { private static final int RUNS = 4; private static final int BUFFER = 1000; // = 0 unbufferd, > 0 buffered ; < 0 unlimited static void skynet(Channelc, long num, int size, int div) throws SuspendExecution, InterruptedException { if (size == 1) { c.send(num); return; } Channel rc = newChannel(BUFFER); long sum = 0L; for (int i = 0; i < div; i++) { long subNum = num + i * (size / div); new Fiber(() -> skynet(rc, subNum, size / div, div)).start(); } for (int i = 0; i < div; i++) sum += rc.receive(); c.send(sum); } public static void main(String[] args) throws Exception { //這里跑4次,是為了讓JVM預(yù)熱好做優(yōu)化,所以我們以最后一個(gè)結(jié)果為準(zhǔn)。 for (int i = 0; i < RUNS; i++) { long start = System.nanoTime(); Channel c = newChannel(BUFFER); new Fiber(() -> skynet(c, 0, 1_000_000, 10)).start(); long result = c.receive(); long elapsed = (System.nanoTime() - start) / 1_000_000; System.out.println((i + 1) + ": " + result + " (" + elapsed + " ms)"); } } }
golang的代碼我就不貼了,大家可以從github上拿到,我這里直接貼出結(jié)果。
platform | time |
---|---|
Golang | 261ms |
Quasar | 612ms |
從Skynet測(cè)試中可以看出,Quasar的性能對(duì)比Golang還是有差距的,但是不應(yīng)該達(dá)到兩倍多吧,經(jīng)過向Quasar作者求證才得知這個(gè)測(cè)試并沒有測(cè)試出實(shí)際性能,只是測(cè)試調(diào)度開銷而已。
因?yàn)閟kynet方法內(nèi)部幾乎沒有做任何事情,只是簡(jiǎn)單的做了一個(gè)加法然后進(jìn)一步的遞歸生成新的Fiber而已,相當(dāng)于只是測(cè)試了Quasar生成并調(diào)度百萬Fiber所需要的時(shí)間而已。而Java里的加法操作開銷遠(yuǎn)比生成Fiber的開銷要低,因此感覺整體性能不如golang(golang的coroutine是語言級(jí)別的)。
實(shí)際上我們?cè)趯?shí)際項(xiàng)目中生成的Fiber中不可能只做一下簡(jiǎn)單的加法就退出,至少要花費(fèi)1ms做一些簡(jiǎn)單的事情吧,(Quasar里Fiber的調(diào)度差不多在us級(jí)別),所以我們考慮在skynet里加一些比較耗時(shí)的操作,比如隨機(jī)生成1000個(gè)整數(shù)并對(duì)其進(jìn)行排序,這樣Fiber里算是有了相應(yīng)的性能開銷,與調(diào)度的開銷相比,調(diào)度的開銷就可以忽略不計(jì)了。(大家可以把調(diào)度開銷想象成不定積分的常數(shù))。
下面我分別為兩種語言了加了數(shù)組排序邏輯,并插在響應(yīng)的Fiber里。
public class Skynet { private static Random random = new Random(); private static final int NUMBER_COUNT = 1000; private static final int RUNS = 4; private static final int BUFFER = 1000; // = 0 unbufferd, > 0 buffered ; < 0 unlimited private static void numberSort() { int[] nums = new int[NUMBER_COUNT]; for (int i = 0; i < NUMBER_COUNT; i++) nums[i] = random.nextInt(NUMBER_COUNT); Arrays.sort(nums); } static void skynet(Channelc, long num, int size, int div) throws SuspendExecution, InterruptedException { if (size == 1) { c.send(num); return; } //加入排序邏輯 numberSort(); Channel rc = newChannel(BUFFER); long sum = 0L; for (int i = 0; i < div; i++) { long subNum = num + i * (size / div); new Fiber(() -> skynet(rc, subNum, size / div, div)).start(); } for (int i = 0; i < div; i++) sum += rc.receive(); c.send(sum); } public static void main(String[] args) throws Exception { for (int i = 0; i < RUNS; i++) { long start = System.nanoTime(); Channel c = newChannel(BUFFER); new Fiber(() -> skynet(c, 0, 1_000_000, 10)).start(); long result = c.receive(); long elapsed = (System.nanoTime() - start) / 1_000_000; System.out.println((i + 1) + ": " + result + " (" + elapsed + " ms)"); } } }
const ( numberCount = 1000 loopCount = 1000000 ) //排序函數(shù) func numberSort() { nums := make([]int, numberCount) for i := 0; i < numberCount; i++ { nums[i] = rand.Intn(numberCount) } sort.Ints(nums) } func skynet(c chan int, num int, size int, div int) { if size == 1 { c <- num return } //加了排序邏輯 numberSort() rc := make(chan int) var sum int for i := 0; i < div; i++ { subNum := num + i*(size/div) go skynet(rc, subNum, size/div, div) } for i := 0; i < div; i++ { sum += <-rc } c <- sum } func main() { c := make(chan int) start := time.Now() go skynet(c, 0, loopCount, 10) result := <-c took := time.Since(start) fmt.Printf("Result: %d in %d ms. ", result, took.Nanoseconds()/1e6) }
platform | time |
---|---|
Golang | 23615ms |
Quasar | 15448ms |
最后再進(jìn)行一次測(cè)試,發(fā)現(xiàn)Java的性能優(yōu)勢(shì)體現(xiàn)出來了。幾乎是golang的1.5倍,這也許是JVM/JDK經(jīng)過多年優(yōu)化的優(yōu)勢(shì)。因?yàn)榧恿藰I(yè)務(wù)邏輯后,對(duì)比的就是各種庫(kù)以及編譯器對(duì)語言的優(yōu)化了,協(xié)程調(diào)度開銷幾乎可以忽略不計(jì)。
為什么協(xié)程在Java里一直那么小眾其實(shí)早在JDK1的時(shí)代,Java的線程被稱為GreenThread,那個(gè)時(shí)候就已經(jīng)有了Fiber,但是當(dāng)時(shí)不能與操作系統(tǒng)實(shí)現(xiàn)N:M綁定,所以放棄了?,F(xiàn)在Quasar憑借ForkJoinPool這個(gè)成熟的線程調(diào)度庫(kù)。
另外,如果你希望你的代碼能夠跑在Fiber里面,需要一個(gè)很大的前提條件,那就是你所有的庫(kù),必須是異步無阻塞的,也就說必須類似于node.js上的庫(kù),所有的邏輯都是異步回調(diào),而自Java里基本上所有的庫(kù)都是同步阻塞的,很少見到異步無阻塞的。而且得益于J2EE,以及Java上的三大框架(SSH)洗腦,大部分Java程序員都已經(jīng)習(xí)慣了基于線程,線性的完成一個(gè)業(yè)務(wù)邏輯,很難讓他們接受一種將邏輯割裂的異步編程模型。
但是隨著異步無阻塞這股風(fēng)氣起來,以及相關(guān)的coroutine語言Golang大力推廣,人們?cè)絹碓街廊绾胃玫恼ジ蒀PU性能(讓CPU避免不必要的等待,減少上下文切換),阻塞的行為基本發(fā)生在I/O上,如果能有一個(gè)庫(kù)能把所有的I/O行為都包裝成異步阻塞的話,那么Quasar就會(huì)有用武之地,JVM上公認(rèn)的是異步網(wǎng)絡(luò)通信庫(kù)是Netty,通過Netty基本解決了網(wǎng)絡(luò)I/O問題,另外還有一個(gè)是文件I/O,而這個(gè)JDK7提供的NIO2就可以滿足,通過AsynchronousFileChannel即可。
剩下的就是如何將他們封裝成更友好的API了。目前能達(dá)到生產(chǎn)級(jí)別的這種異步工具庫(kù),JVM上只有Vert.x3,封裝了Netty4,封裝了AsynchronousFileChannel,而且Vert.x官方也出了一個(gè)相對(duì)應(yīng)的封裝了Quasar的庫(kù)vertx-sync。
Quasar目前是由一家商業(yè)公司Parallel Universe控制著,且有自己的一套體系,包括Quasar-actor,Quasar-galaxy等各個(gè)模塊,但是Quasar-core是開源的,此外Quasar自己也通過Fiber封裝了很多的第三方庫(kù),目前全都在comsat這個(gè)項(xiàng)目里。隨便找一個(gè)項(xiàng)目看看,你會(huì)發(fā)現(xiàn)其實(shí)通過Quasar的Fiber去封裝第三方的同步庫(kù)還是很簡(jiǎn)單的。
寫在最后異步無阻塞的編碼方式其實(shí)有很多種實(shí)現(xiàn),比如node.js的提倡的Promise,對(duì)應(yīng)到Java8的就是CompletableFuture。
另外事件響應(yīng)式也算是一個(gè)比較流行的做法,比如ReactiveX系列,RxJava,Rxjs,RxSwift,等。我個(gè)人覺得RxJava是一個(gè)非常好的函數(shù)式響應(yīng)實(shí)現(xiàn)(JDK9會(huì)有對(duì)應(yīng)的JDK實(shí)現(xiàn)),但是我們不能要求所有的程序員一眼就提煉出業(yè)務(wù)里的functor,monad(這些能力需要長(zhǎng)期浸淫在函數(shù)式編程思想里),反而RxJava特別適合用在前端與用戶交互的部分,因?yàn)橛脩舻狞c(diǎn)擊滑動(dòng)行為是一個(gè)個(gè)真實(shí)的事件流,這也是為什么RxJava在Android端非?;鸬脑颍蠖嘶旧隙际峭ㄟ^Rest請(qǐng)求過來,每一個(gè)請(qǐng)求其實(shí)已經(jīng)限定了業(yè)務(wù)范圍,不會(huì)再有復(fù)雜的事件邏輯,所以基本上RxJava在Vert.x這端只是做了一堆的flatmap,再加上微服務(wù)化,所有的業(yè)務(wù)邏輯都已經(jīng)做了最小的邊界,所以順序的同步的編碼方式更適合寫業(yè)務(wù)邏輯的后端程序員。
所以這里Golang開了個(gè)好頭,但是Golang也有其自身的限制,比如不支持泛型,當(dāng)然這個(gè)仁者見仁智者見智了,包的依賴管理比較弱,此外Golang沒有線程池的概念,如果coroutine里的邏輯發(fā)生了阻塞,那么整個(gè)程序會(huì)hang死。而這點(diǎn)Vert.x提供了一個(gè)Worker Pool的概念,可以將需要耗時(shí)執(zhí)行的邏輯包到線程池里面,執(zhí)行完后異步返回給EventLoop線程。
下一篇我們來研究一下vertx-sync,讓vert.x里所有的異步編碼方式同步化,徹底解決Vert.x里的Callback Hell。
本文系力譜宿云LeapCloud旗下 MaxLeap 團(tuán)隊(duì)成員:劉小溪【原創(chuàng)】,轉(zhuǎn)載請(qǐng)務(wù)必注明作者及原創(chuàng)地址
原創(chuàng)首發(fā)地址:https://blog.maxleap.cn/archi...
第二篇續(xù)作:次時(shí)代Java編程(一):續(xù) vertx-sync實(shí)踐
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/65986.html
摘要:因?yàn)槎嗑€程競(jìng)爭(zhēng)鎖時(shí)會(huì)引起上下文切換。減少線程的使用。舉個(gè)例子如果說服務(wù)器的帶寬只有,某個(gè)資源的下載速度是,系統(tǒng)啟動(dòng)個(gè)線程下載該資源并不會(huì)導(dǎo)致下載速度編程,所以在并發(fā)編程時(shí),需要考慮這些資源的限制。 最近私下做一項(xiàng)目,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是項(xiàng)目不存在bug,與其懼怕,何不與其剛正面。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Jav...
摘要:相比與其他操作系統(tǒng)包括其他類系統(tǒng)有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文切換和模式切換的時(shí)間消耗非常少。因?yàn)槎嗑€程競(jìng)爭(zhēng)鎖時(shí)會(huì)引起上下文切換。減少線程的使用。很多編程語言中都有協(xié)程。所以如何避免死鎖的產(chǎn)生,在我們使用并發(fā)編程時(shí)至關(guān)重要。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)syn...
摘要:定時(shí)器例子之前通過調(diào)用定時(shí)器,需要傳一個(gè)回調(diào),然后所有的代碼邏輯都包在里面。這里定時(shí)器會(huì)阻塞在這一行,直到一秒后才會(huì)執(zhí)行下面的一行。 之前介紹過quasar,如果你希望在vert.x項(xiàng)目里使用coroutine的話,建議使用vertx-sync。本篇將介紹vertx-sync。 showImg(/img/bVzIsu); 本來打算另起一篇,寫其他方面的東西,但是最近比較忙,就先寫一篇實(shí)...
摘要:特別是最火的協(xié)程框架也無法保存狀態(tài),讓人非常惋惜。但是因?yàn)闂5谋旧頍o法持久化,所以也就無法持久化。其難度在于,假設(shè)整個(gè)要持久化的調(diào)用棧全部都是內(nèi)的,比如純的。采取的是暴力地把整個(gè)棧區(qū)域拷貝到上的方式來保存其狀態(tài)。 python主流的協(xié)程實(shí)現(xiàn)有五種: cPython的generator cPython的greenlet cPython的fibers stackless python ...
摘要:用基于快速實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的代理服務(wù)器,只需要分鐘時(shí)間。監(jiān)控統(tǒng)計(jì)客戶端的請(qǐng)求情況,請(qǐng)求分布統(tǒng)計(jì)請(qǐng)求類型等,以此來優(yōu)化數(shù)據(jù)庫(kù)的使用。 用java8基于vert.x3 快速實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的mysql代理服務(wù)器,只需要5分鐘時(shí)間。 showImg(/img/bVz0vh); 什么是mysql 代理? mysql代理是介于client端和mysql服務(wù)端中間層服務(wù),如下圖所示: showImg(...
閱讀 3374·2021-10-11 11:08
閱讀 4494·2021-09-22 15:54
閱讀 974·2019-08-30 15:56
閱讀 919·2019-08-30 15:55
閱讀 3599·2019-08-30 15:52
閱讀 1421·2019-08-30 15:43
閱讀 1992·2019-08-30 11:14
閱讀 2563·2019-08-29 16:11