考慮以下開發框架,一組網絡接收goroutine接收網絡包,解包,而後將邏輯包推送到消息隊列,由一個單一的邏輯處理goroutine負責從隊列中提取邏輯包並處理(這樣主處理邏輯中基本上不用考慮多線程競爭的鎖問題了)。node
若是邏輯包的處理涉及到調用可能會阻塞的函數調用怎麼辦,若是在處理函數中直接調用這樣的函數將致使邏輯處理goroutine被阻塞,沒法繼續處理隊列中被排隊的數據包,這將嚴重下降服務的處理能力。git
一種方式是啓動一個新的go程去執行阻塞調用,並註冊回調函數,當阻塞調用返回後將回調閉包從新push到消息對列中,由邏輯處理goroutine繼續處理後續邏輯。但我本人不大喜歡在邏輯處理上使用回調的方式(node的callback hell)。我但願能夠線性的編寫邏輯代碼。github
爲了實現這個目的,我須要一個相似lua的單線程協做式coroutine調度機制,單線程讓使用者不用擔憂數據競爭,協做式可讓coroutine在執行異步調用前將執行權交出去,等異步結果返回後再將執行權切換回來,線性的執行後續代碼。redis
可是,goroutine天生就是多線程調度執行的,有辦法實現這個目標嗎?答案是確定的。安全
咱們能夠實現一個邏輯上的單線程,從全局上看,只有惟一一個goroutine能夠執行邏輯處理代碼。核心思想就是由調度器從任務隊列中提取任務,挑選一個空閒的goroutine,將其喚醒並讓本身阻塞,當goroutine須要阻塞時就喚醒調度器並將本身阻塞。這樣全局上就只有惟一的goroutine在執行邏輯代碼。網絡
下面是一個使用示例:多線程
package main import ( "fmt" "time" "coop-go" ) func main() { count := int32(0) var p *coop.CoopScheduler p = coop.NewCoopScheduler(func (e interface{}){ count++ if count >= 30000000 { p.Close() return } //調用阻塞函數 p.Call(func () { time.Sleep(time.Millisecond * time.Duration(10)) }) //繼續投遞任務 p.PostEvent(1) }) for i := 0; i < 10000; i++ { //投遞任務 p.PostEvent(1) } p.Start() fmt.Printf("scheduler stop,total taskCount:%d\n",c2) }
首先用一個任務處理函數做爲參數建立調度器。而後向調度器投遞任務觸發處理循環,最後啓動處理。閉包
這裏惟一須要關注的是Call,它的參數是一個函數閉包,Call將會在並行的環境下執行傳給它的閉包(不釋放本身執行權的同時喚醒調度器去調度其它任務),由於這個閉包是並行執行的,因此閉包內不能含有任何非
線程安全的代碼,能夠將同步的阻塞調用放到閉包中,不用擔憂阻塞主處理邏輯。Call內部在閉包調用返回以後會將本身阻塞並添加到喚醒隊列中等待調度器調度運行。得到運行權以後才從Call調用返回,從Call返回
以後,又回到線程安全的運行環境下。框架
下面是一個同步獲取redis數據的調用:異步
ret Call(func() { /* *這裏面不能有任何線程不安全的代碼,只是一個簡單的函數調用 */ ret = redis.get() }) if ret { //根據返回值執行處理邏輯 }
在個人 i5 雙核 2.5GHz mac mini上每秒鐘能夠執行100W次的調度,雖然跟C協程數千萬的調度次數無法比,可是也基本夠用了,畢竟在實現的使用中,每秒能處理10W的請求已經至關不錯了。
更多使用示例請見coop-go-exampe