Go語言實戰筆記(十五)| Go 並發樣本-Runner

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

這篇通過一個例子,示範使用通道來監控程式的執行時間,生命週期,甚至終止程式等。我們這個程式叫runner,我們可以稱之為執行者,它可以在後台執行任何任務,而且我們還可以控制這個執行者,比如強制終止它等。

現在開始吧,運用我們前面十幾篇連載的知識,來構建我們的Runner,使用一個結構體類型就可以。

123456789
//一個執行者,可以執行任何任務,但是這些任務是限制完成的,//該執行者可以通過發送終止訊號終止它type Runner struct {tasks []func(int) //要執行的任務complete chan error //用於通知任務全部完成timeout <-chan time.Time //這些任務在多久內完成interrupt chan os.Signal //可以控制強制終止的訊號}

樣本中,我們定義了一個結構體類型Runner,這個Runner包含了要執行哪些任務tasks,然後使用complete通知任務是否全部完成,不過這個執行者是有時間限制的,這就是timeout,如果在限定的時間內沒有完成,就會接收到逾時的通知,如果完成了就會接收到完成的通知。注意這裡的timeout是單向通道,只能接收。

complete定義為error類型的通道,是為了當執行任務出現問題時返回錯誤的原因,如果沒有出現錯誤,返回的是nil

此外,我們還定義了一個中斷的訊號,讓我們可以隨時的終止執行者。

有了結構體,我們接著再定義一個工廠函數New,用於返回我們需要的Runner

1234567
func New(tm time.Duration) *Runner {return &Runner{complete:make(chan error),timeout:time.After(tm),interrupt:make(chan os.Signal,1),}}

這個New函數非常簡潔,可以幫我們很快的初始化一個Runnner,它只有一個參數,用來設定這個執行者的逾時時間。這個逾時區間被我們傳遞給了time.After函數,這個函數可以在tm時間後,會同夥一個time.Time類型的只能接收的單向通道,來告訴我們已經到時間了。

complete是一個無緩衝通道,也就是同步通道,因為我們要使用它來控制我們整個程式是否終止,所以它必須是同步通道,要讓main routine等待,一致要任務完成或者被強制終止。

interrupt是一個有緩衝的通道,這樣做是因為,我們可以至少接收到一個作業系統的中斷資訊,這樣Go runtime在發送這個訊號的時候不會被阻塞,如果是無緩衝的通道就會阻塞了。

系統訊號是什麼意思呢,比如我們在程式執行的時候按下Ctrl + C,這就是一個中斷的訊號,告訴程式可以強制終止了。

我們這裡初始化了結構體的三個欄位,而執行的任務tasks沒有初始化,預設就是零值nil,因為它是一個切片。但是我們的執行者Runner不能沒有任務啊,既然初始化Runner的時候沒有,那我們就定義一個方法,通過方法給執行者添加需要執行的任務。

1234
//將需要執行的任務,添加到Runner裡func (r *Runner) Add(tasks ...func(int)){r.tasks = append(r.tasks,tasks...)}

這個沒有太多可以說明的,r.tasks就是一個切片,來儲存需要執行的任務。通過內建的append函數就可以追加任務了。這裡使用了可變參數,可以靈活的添加一個,甚至同時多個任務,比較方便。

到了這裡我們需要的執行者Runner,如何新增工作,如何擷取一個執行者,都有了,下面就開始執行者如何運行任務?如何在啟動並執行時候強制中斷任務?在這些處理之前,我們先來定義兩個我們的兩個錯誤變數,以便在接下來的代碼執行個體中使用。

12
var ErrTimeOut = errors.New("執行者執行逾時")var ErrInterrupt = errors.New("執行者被中斷")

兩種錯誤類型,一個表示因為逾時錯誤,一個表示因為被中斷錯誤。下面我們就看看如何執行一個個任務。

12345678910111213141516171819202122
//執行任務,執行的過程中接收到中斷訊號時,返回中斷錯誤//如果任務全部執行完,還沒有接收到中斷訊號,則返回nilfunc (r *Runner) run() error {for id, task := range r.tasks {if r.isInterrupt() {return ErrInterrupt}task(id)}return nil}//檢查是否接收到了中斷訊號func (r *Runner) isInterrupt() bool {select {case <-r.interrupt:signal.Stop(r.interrupt)return truedefault:return false}}

新增的run方法也很簡單,會使用for迴圈,不停的運行任務,在啟動並執行每個任務之前,都會檢測是否收到了中斷訊號,如果沒有收到,則繼續執行,一直到執行完畢,返回nil;如果收到了中斷訊號,則直接返回中斷錯誤類型,任務執行終止。

這裡注意isInterrupt函數,它在實現的時候,使用了基於select的多工,selectswitch很像,只不過它的每個case都是一個通訊操作。那麼到底選擇哪個case塊執行呢?原則就是哪個case的通訊操作可以執行就執行哪個,如果同時有多個可以執行的case,那麼就隨機播放一個執行。

針對我們方法中,如果r.interrupt中接受不到值,就會執行default語句塊,返回false,一旦r.interrupt中可以接收值,就會通知Go Runtime停止接收中斷訊號,然後返回true

這裡如果沒有default的話,select是會阻塞的,直到r.interrupt可以接收值為止,因為我們例子中的邏輯要求不能阻塞,所以我們使用了default

好了,基礎工作都做好了,現在開始執行我們所有的任務,並且時刻監視著任務的完成,執行事件的逾時。

12345678910111213141516
//開始執行所有任務,並且監視通道事件func (r *Runner) Start() error {//希望接收哪些系統訊號signal.Notify(r.interrupt, os.Interrupt)go func() {r.complete <- r.run()}()select {case err := <-r.complete:return errcase <-r.timeout:return ErrTimeOut}}

signal.Notify(r.interrupt, os.Interrupt),這個是表示,如果有系統中斷的訊號,發給r.interrupt即可。

任務的執行,這裡開啟了一個groutine,然後調用run方法,結果發送給通道r.complete。最後就是使用一個select多工,哪個通道可以操作,就返回哪個。

到了這時候,只有兩種情況了,要麼任務完成;要麼到時間了,任務執行逾時。從我們前面的代碼看,任務完成又分兩種情況,一種是沒有執行完,但是收到了中斷訊號,中斷了,這時返回中斷錯誤;一種是順利執行完成,這時返回nil。

現在把這些代碼匯總一下,容易統一理解一下,所有代碼如下

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
package commonimport ("errors""os""os/signal""time")var ErrTimeOut = errors.New("執行者執行逾時")var ErrInterrupt = errors.New("執行者被中斷")//一個執行者,可以執行任何任務,但是這些任務是限制完成的,//該執行者可以通過發送終止訊號終止它type Runner struct {tasks     []func(int)      //要執行的任務complete  chan error       //用於通知任務全部完成timeout   <-chan time.Time //這些任務在多久內完成interrupt chan os.Signal   //可以控制強制終止的訊號}func New(tm time.Duration) *Runner {return &Runner{complete:  make(chan error),timeout:   time.After(tm),interrupt: make(chan os.Signal, 1),}}//將需要執行的任務,添加到Runner裡func (r *Runner) Add(tasks ...func(int)) {r.tasks = append(r.tasks, tasks...)}//執行任務,執行的過程中接收到中斷訊號時,返回中斷錯誤//如果任務全部執行完,還沒有接收到中斷訊號,則返回nilfunc (r *Runner) run() error {for id, task := range r.tasks {if r.isInterrupt() {return ErrInterrupt}task(id)}return nil}//檢查是否接收到了中斷訊號func (r *Runner) isInterrupt() bool {select {case <-r.interrupt:signal.Stop(r.interrupt)return truedefault:return false}}//開始執行所有任務,並且監視通道事件func (r *Runner) Start() error {//希望接收哪些系統訊號signal.Notify(r.interrupt, os.Interrupt)go func() {r.complete <- r.run()}()select {case err := <-r.complete:return errcase <-r.timeout:return ErrTimeOut}}

這個common包裡的Runner我們已經開發完了,現在我們寫個例子試試它。

123456789101112131415161718192021222324252627282930313233343536
package mainimport ("flysnow.org/hello/common""log""time""os")func main() {log.Println("...開始執行任務...")timeout := 3 * time.Secondr := common.New(timeout)r.Add(createTask(), createTask(), createTask())if err:=r.Start();err!=nil{switch err {case common.ErrTimeOut:log.Println(err)os.Exit(1)case common.ErrInterrupt:log.Println(err)os.Exit(2)}}log.Println("...任務執行結束...")}func createTask() func(int) {return func(id int) {log.Printf("正在執行任務%d", id)time.Sleep(time.Duration(id)* time.Second)}}

例子非常簡單,定義任務逾時時間為3秒,添加3個產生的任務,每個任務都是列印一個正在執行哪個任務,然後休眠一段時間。

調用r.Start()開始執行任務,如果一切都正常的話,返回nil,然後列印出...任務執行結束...,不過我們例子中,因為逾時時間和任務的設定,結果是執行逾時的。

12345
2017/04/15 22:17:55 ...開始執行任務...2017/04/15 22:17:55 正在執行任務02017/04/15 22:17:55 正在執行任務12017/04/15 22:17:56 正在執行任務22017/04/15 22:17:58 執行者執行逾時

如果我們把逾時時間改為4秒或者更多,就會列印...任務執行結束...。這裡我們還可以測試另外一種系統中斷情況,在終端裡運行程式後,快速不停的按Ctrl + C,就可以看到執行者被中斷的列印輸出資訊了。

到這裡,這篇文章已經要收尾了,這個例子中,我們示範使用通道通訊、同步等待,監控程式等。

此外這個執行者也是一個很不錯的模式,比如我們寫好之後,交給定時任務去執行即可,比如cron,這個模式我們還可以擴充,更高效率的並發,更多靈活的控製程序的生命週期,更高效的監控等,這個大家自己可以試試,基於自己的需求修改就可以了。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.