1. 程式人生 > >C++ 非同步程式設計探索(二) thread safe

C++ 非同步程式設計探索(二) thread safe

前言

在非同步程式設計實踐中,曾經遇到的最大的問題就是thread safe 問題。

我所在的團隊是Database團隊,主要是為APP提供和redis互動的API。場景就是application thread呼叫我們的API。我們有個worker thread負責網路I/O。我們的API基於hiredis,hiredis宣告自己不是thread-safe。
這也很正常,看了看hireids的程式碼,關於網路通訊的部分,其中有個i-buffer和一個o-buffer這兩個buffer負責快取和網路通訊的資訊。hiredis將“poll”這部分做了抽象,可以相容很多非同步框架比如redis的ae,libevent等等。hiredis的做法是當網路可寫的時候,呼叫send,如果send沒有將o-buf傳送完全時,呼叫eladdwrite,告訴系統,網路可寫時告訴我。
那麼為什麼它不是執行緒安全的呢? 首先如果要做到執行緒安全,這兩個buffer就需要“加鎖”來防止兩個執行緒通知操作相同的buffer,但是hiredis中沒有相應的處理程式碼


既然hiredis沒有提供執行緒安全的API,那麼如何解決這個問題呢?

非同步事件通知機制

非同步程式設計無法避免都要面對多個執行緒,這個就要涉及到執行緒之間的通訊。解決方法有很多,比如上篇文章中提到過的boost furure-then, 但是這樣的缺點是每個then多會新建一個執行緒處理task,起執行緒會很耗資源。所以有人就會想,一開始就建立多個執行緒形成一個執行緒池,這樣就消除了不斷起執行緒的損耗。這確實是一個好辦法,但是這也引入一個問題–如何拖住這個執行緒,不讓執行緒退出。解決方法同樣有很多。比如用condition wait,poll…
至此就正式引出非同步事件通知機制。非同步事件通知機制有很多種類,其大多數都是基於epoll kqueue等linux底層API。下面我就舉幾個比較著名的框架:

libevent

libevent是我最早接觸的此類框架,其支援linux各種file discriptor(fd),之前寫了一個小demo。link:https://github.com/maxcong001/task_base
想法就是運用libevent作為poll機制,監聽eventfd。APP執行緒當有事情的時候就將事情push到一個queue中(加鎖),同時eventfd加一,這樣libeventthread就會被喚醒,看看有多少事件需要處理,並從queue中提取相應的事情並處理。

libuv

libuv和libevent相似,相比libevent更加輕量,同時實現稍有不同,同樣我寫了一個小的demo,link:

https://github.com/maxcong001/task_base_libuv
這裡libuv支援uv_async_send這樣的API不用我們再去監聽fd。同時這個小demo中還用的執行緒安全的無鎖queue(以後會寫有關無鎖佇列的文章,這裡就不介紹了)。

boost::asio::io_service(io_context 新版本boost)

boost這個神器總不會讓人失望,io_service抽象了底層I/O, 所有非同步io事件都是通過它來分發處理的,當然也包括我們需要的非同步事件通知機制,主要我們可以呼叫post方法, post用於釋出io事件,如timer,socket讀寫等,一般由asio框架相應物件呼叫,無需我們顯式呼叫。新版本的io_context 的post直接可以帶handler。本來寫了個小程式,由於種種原因不能在公司之外分享。

非同步事件資料的執行緒安全

兩個執行緒之間互動,不可避免的是共享一些資料結構,上一個章節,講到libevent時,提到了一個queue,這裡存著要處理的任務,由於可能有多個執行緒操作這個queue,為了解決這個問題,簡單粗暴的加鎖。

眾所周知加鎖是一個很耗的操作。同時等待鎖也很絕望,用不好還會產生死鎖
上一章節講到libev時就升級了一下用了無鎖queue
當講到asio時候更進一步。這裡我們可以使用lambda函式捕獲一些資料,關於捕獲,C++11以後我們會談更多

非同步事件結果的返回

對於結果的返回,有兩種例子,第一種就是結果在當前執行緒中直接處理,第二種就是將結果傳回發起task的執行緒

傳回發起task的執行緒

如果兩個執行緒都是訊息驅動的, 可以發一個訊息給原執行緒。
如果只有一個執行緒是訊息驅動的,可以參考future.get()。當需要用某個變數時,呼叫get,但這可能會阻塞。

在當前執行緒處理

這就涉及到一個程式設計思想的問題。如何將一個task做到thread不感知。