libuv的多線程之間傳遞消息


官網上給出的例子http://nikhilm.github.io/uvbook/threads.html#inter-thread-communication,中文理解在后邊

Inter-thread communication

Sometimes you want various threads to actually send each other messages while they are running. For example you might be running some long duration task in a separate thread (perhaps using uv_queue_work) but want to notify progress to the main thread. This is a simple example of having a download manager informing the user of the status of running downloads.

progress/main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
uv_loop_t *loop; uv_async_t async; int main() { loop = uv_default_loop(); uv_work_t req; int size = 10240; req.data = (void*) &size;  uv_async_init(loop, &async, print_progress); uv_queue_work(loop, &req, fake_download, after); return uv_run(loop, UV_RUN_DEFAULT); } 

The async thread communication works on loops so although any thread can be the message sender, only threads with libuv loops can be receivers (or rather the loop is the receiver). libuv will invoke the callback (print_progress) with the async watcher whenever it receives a message.

Warning

It is important to realize that the message send is async, the callback may be invoked immediately after uv_async_send is called in another thread, or it may be invoked after some time. libuv may also combine multiple calls to uv_async_send and invoke your callback only once. The only guarantee that libuv makes is – The callback function is called at least once after the call to uv_async_send. If you have no pending calls to uv_async_send, the callback won’t be called. If you make two or more calls, and libuv hasn’t had a chance to run the callback yet, it may invoke your callback only once for the multiple invocations of uv_async_send. Your callback will never be called twice for just one event.

progress/main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void fake_download(uv_work_t *req) { int size = *((int*) req->data); int downloaded = 0; double percentage; while (downloaded < size) { percentage = downloaded*100.0/size;  async.data = (void*) &percentage;  uv_async_send(&async); sleep(1); downloaded += (200+random())%1000; // can only download max 1000bytes/sec, // but at least a 200; } } 

In the download function we modify the progress indicator and queue the message for delivery with uv_async_send. Remember: uv_async_send is also non-blocking and will return immediately.

progress/main.c

1
2
3
4
void print_progress(uv_async_t *handle, int status /*UNUSED*/) { double percentage = *((double*) handle->data); fprintf(stderr, "Downloaded %.2f%%\n", percentage); } 

The callback is a standard libuv pattern, extracting the data from the watcher.

Finally it is important to remember to clean up the watcher.

progress/main.c

1
2
3
4
void after(uv_work_t *req, int status) { fprintf(stderr, "Download complete\n");  uv_close((uv_handle_t*) &async, NULL); } 

After this example, which showed the abuse of the data field, bnoordhuis pointed out that using the data field is not thread safe, and uv_async_send() is actually only meant to wake up the event loop. Use a mutex or rwlock to ensure accesses are performed in the right order.

 

這個也有中文版的翻譯,可以網上搜到。

文中最后提到的uv_async_t.data不是線程安全的,我的理解如下:

在主線程中開啟一個消息循環loop(uv_loop_t對象),然后為loop注冊了一個異步消息監聽器async(uv_async_t對象),其他線程就可以通過async給主線程發送消息。做法就是把數據保存在async下,然后將async發給loop,loop異步的獲取async,然后從async中得到數據並處理。我想說的是,async對象只有一個,在傳遞的過程中沒有副本,因此從發送消息到消息被獲取並處理完畢,這期間async對象都是線程不安全的,應該加上同步機制來保證整個期間async對象只服務於一個其他線程。但是如果同步的話,那libuv提供的異步消息機制豈不沒有用了?我們的多線程程序也會在此遇到瓶頸。

我的解決方法有兩個:

1.其他線程在每次向主線程發送消息的時候,都新建一個async對象,並臨時注冊到loop里;當loop獲取該async對象,處理完畢后,注銷並並刪除async對象

2.同時注冊多個async對象,將他們保存在隊列中。其他線程在向主線程發送消息時,互斥的獲取其中一個async對象,等loop處理完該async對象后,將其互斥的插回隊列。雖然這樣也有同步操作,但它的同步周期大大減小,提高了線程並行度。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM