<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                #Threads 等一下!為什么我們要聊線程?事件循環(event loop)不應該是用來做web編程的方法嗎?(如果你對event loop, 不是很了解,可以看[這里](http://www.ruanyifeng.com/blog/2014/10/event-loop.html))。哦,不不。線程依舊是處理器完成任務的重要手段。線程因此有可能會派上用場,雖然會使得你不得不艱難地應對各種原始的同步問題。 線程會在內部使用,用來在執行系統調用時偽造異步的假象。libuv通過線程還可以使得程序異步地執行一個阻塞的任務。方法就是大量地生成新線程,然后收集線程執行返回的結果。 當下有兩個占主導地位的線程庫:windows下的線程實現和POSIX的[pthread](http://man7.org/linux/man-pages/man7/pthreads.7.html)。libuv的線程API與pthread的API在使用方法和語義上很接近。 值得注意的是,libuv的線程模塊是自成一體的。比如,其他的功能模塊都需要依賴于event loop和回調的原則,但是線程并不是這樣。它們是不受約束的,會在需要的時候阻塞,通過返回值產生信號錯誤,還有像接下來的這個例子所演示的這樣,不需要在event loop中執行。 因為線程API在不同的系統平臺上,句法和語義表現得都不太相似,在支持程度上也各不相同。考慮到libuv的跨平臺特性,libuv支持的線程API個數很有限。 最后要強調一句:只有一個主線程,主線程上只有一個event loop。不會有其他與主線程交互的線程了。(除非使用`uv_async_send`)。 ##Core thread operations 下面這個例子不會很復雜,你可以使用`uv_thread_create()`開始一個線程,再使用`uv_thread_join()`等待其結束。 ####thread-create/main.c ```c int main() { int tracklen = 10; uv_thread_t hare_id; uv_thread_t tortoise_id; uv_thread_create(&hare_id, hare, &tracklen); uv_thread_create(&tortoise_id, tortoise, &tracklen); uv_thread_join(&hare_id); uv_thread_join(&tortoise_id); return 0; } ``` #####TIP >在Unix上``uv_thread_t``只是``pthread_t``的別名, 但是這只是一個具體實現,不要過度地依賴它,認為這永遠是成立的。 `uv_thread_t`的第二個參數指向了要執行的函數的地址。最后一個參數用來傳遞自定義的參數。最終,函數hare將在新的線程中執行,由操作系統調度。 ####thread-create/main.c ```c void hare(void *arg) { int tracklen = *((int *) arg); while (tracklen) { tracklen--; sleep(1); fprintf(stderr, "Hare ran another step\n"); } fprintf(stderr, "Hare done running!\n"); } ``` `uv_thread_join`不像`pthread_join`那樣,允許線線程通過第二個參數向父線程返回值。想要傳遞值,必須使用線程間通信[Inter-thread communication](#inter_thread_communication-pane)。 ##Synchronization Primitives 因為本教程重點不在線程,所以我只羅列了libuv API中一些神奇的地方。剩下的你可以自行閱讀pthreads的手冊。 ####Mutexes libuv上的互斥量函數與pthread上存在一一映射。如果對pthread上的mutex不是很了解可以看[這里](https://computing.llnl.gov/tutorials/pthreads/)。 ####libuv mutex functions ```c UV_EXTERN int uv_mutex_init(uv_mutex_t* handle); UV_EXTERN void uv_mutex_destroy(uv_mutex_t* handle); UV_EXTERN void uv_mutex_lock(uv_mutex_t* handle); UV_EXTERN int uv_mutex_trylock(uv_mutex_t* handle); UV_EXTERN void uv_mutex_unlock(uv_mutex_t* handle); ``` `uv_mutex_init`與`uv_mutex_trylock`在成功執行后,返回0,或者在錯誤時,返回錯誤碼。 如果libuv在編譯的時候開啟了調試模式,`uv_mutex_destroy()`, `uv_mutex_lock()` 和 `uv_mutex_unlock()`會在出錯的地方調用`abort()`中斷。類似的,`uv_mutex_trylock()`也同樣會在錯誤發生時中斷,而不是返回`EAGAIN`和`EBUSY`。 遞歸地調用互斥量函數在某些系統平臺上是支持的,但是你不能太過度依賴。因為例如在BSD上遞歸地調用互斥量函數會返回錯誤,比如你準備使用互斥量函數給一個已經上鎖的臨界區再次上鎖的時候,就會出錯。比如,像下面這個例子: ```c uv_mutex_lock(a_mutex); uv_thread_create(thread_id, entry, (void *)a_mutex); uv_mutex_lock(a_mutex); // more things here ``` 可以用來等待其他線程初始化一些變量然后釋放`a_mutex`鎖,但是第二次調用`uv_mutex_lock()`, 在調試模式下會導致程序崩潰,或者是返回錯誤。 #####NOTE >在linux中是支持遞歸上鎖的,但是在libuv的API中并未實現。 ####Lock 讀寫鎖是更細粒度的實現機制。兩個讀者線程可以同時從共享區中讀取數據。當讀者以讀模式占有讀寫鎖時,寫者不能再占有它。當寫者以寫模式占有這個鎖時,其他的寫者或者讀者都不能占有它。讀寫鎖在數據庫操作中非常常見,下面是一個玩具式的例子: ####ocks/main.c - simple rwlocks ```c #include <stdio.h> #include <uv.h> uv_barrier_t blocker; uv_rwlock_t numlock; int shared_num; void reader(void *n) { int num = *(int *)n; int i; for (i = 0; i < 20; i++) { uv_rwlock_rdlock(&numlock); printf("Reader %d: acquired lock\n", num); printf("Reader %d: shared num = %d\n", num, shared_num); uv_rwlock_rdunlock(&numlock); printf("Reader %d: released lock\n", num); } uv_barrier_wait(&blocker); } void writer(void *n) { int num = *(int *)n; int i; for (i = 0; i < 20; i++) { uv_rwlock_wrlock(&numlock); printf("Writer %d: acquired lock\n", num); shared_num++; printf("Writer %d: incremented shared num = %d\n", num, shared_num); uv_rwlock_wrunlock(&numlock); printf("Writer %d: released lock\n", num); } uv_barrier_wait(&blocker); } int main() { uv_barrier_init(&blocker, 4); shared_num = 0; uv_rwlock_init(&numlock); uv_thread_t threads[3]; int thread_nums[] = {1, 2, 1}; uv_thread_create(&threads[0], reader, &thread_nums[0]); uv_thread_create(&threads[1], reader, &thread_nums[1]); uv_thread_create(&threads[2], writer, &thread_nums[2]); uv_barrier_wait(&blocker); uv_barrier_destroy(&blocker); uv_rwlock_destroy(&numlock); return 0; } ``` 試著來執行一下上面的程序,看讀者有多少次會同步執行。在有多個寫者的時候,調度器會給予他們高優先級。因此,如果你加入兩個讀者,你會看到所有的讀者趨向于在讀者得到加鎖機會前結束。 在上面的例子中,我們也使用了屏障。因此主線程來等待所有的線程都已經結束,最后再將屏障和鎖一塊回收。 ####Others libuv同樣支持[信號量](https://en.wikipedia.org/wiki/Semaphore_programming),[條件變量](https://en.wikipedia.org/wiki/Monitor_synchronization#Waiting_and_signaling)和[屏障](https://en.wikipedia.org/wiki/Barrier_computer_science),而且API的使用方法和pthread中的用法很類似。(如果你對上面的三個名詞還不是很熟,可以看[這里](http://www.wuzesheng.com/?p=1668),[這里](http://name5566.com/4535.html),[這里](http://www.cnblogs.com/panhao/p/4653623.html))。 還有,libuv提供了一個簡單易用的函數`uv_once()`。多個線程調用這個函數,參數可以使用一個uv_once_t和一個指向特定函數的指針,**最終只有一個線程能夠執行這個特定函數,并且這個特定函數只會被調用一次**: ```c /* Initialize guard */ static uv_once_t once_only = UV_ONCE_INIT; int i = 0; void increment() { i++; } void thread1() { /* ... work */ uv_once(once_only, increment); } void thread2() { /* ... work */ uv_once(once_only, increment); } int main() { /* ... spawn threads */ } ``` 當所有的線程執行完畢時,`i == 1`。 在libuv的v0.11.11版本里,推出了uv_key_t結構和操作[線程局部存儲TLS](http://baike.baidu.com/view/598128.htm)的[API](http://docs.libuv.org/en/v1.x/threading.html#thread-local-storage),使用方法同樣和pthread類似。 ##libuv work queue `uv_queue_work()`是一個便利的函數,它使得一個應用程序能夠在不同的線程運行任務,當任務完成后,回調函數將會被觸發。它看起來好像很簡單,但是它真正吸引人的地方在于它能夠使得任何第三方的庫都能以event-loop的方式執行。當使用event-loop的時候,最重要的是不能讓loop線程阻塞,或者是執行高cpu占用的程序,因為這樣會使得loop慢下來,loop event的高效特性也不能得到很好地發揮。 然而,很多帶有阻塞的特性的程序(比如最常見的I/O)使用開辟新線程來響應新請求(最經典的‘一個客戶,一個線程‘模型)。使用event-loop可以提供另一種實現的方式。libuv提供了一個很好的抽象,使得你能夠很好地使用它。 下面有一個很好的例子,靈感來自<<[nodejs is cancer](http://teddziuba.github.io/2011/10/node-js-is-cancer.html)>>。我們將要執行fibonacci數列,并且睡眠一段時間,但是將阻塞和cpu占用時間長的任務分配到不同的線程,使得其不會阻塞event loop上的其他任務。 ####queue-work/main.c - lazy fibonacci ```c void fib(uv_work_t *req) { int n = *(int *) req->data; if (random() % 2) sleep(1); else sleep(3); long fib = fib_(n); fprintf(stderr, "%dth fibonacci is %lu\n", n, fib); } void after_fib(uv_work_t *req, int status) { fprintf(stderr, "Done calculating %dth fibonacci\n", *(int *) req->data); } ``` 任務函數很簡單,也還沒有運行在線程之上。`uv_work_t`是關鍵線索,你可以通過`void *data`傳遞任何數據,使用它來完成線程之間的溝通任務。但是你要確信,當你在多個線程都在運行的時候改變某個東西的時候,能夠使用適當的鎖。 觸發器是`uv_queue_work`: ####queue-work/main.c ```c int main() { loop = uv_default_loop(); int data[FIB_UNTIL]; uv_work_t req[FIB_UNTIL]; int i; for (i = 0; i < FIB_UNTIL; i++) { data[i] = i; req[i].data = (void *) &data[i]; uv_queue_work(loop, &req[i], fib, after_fib); } return uv_run(loop, UV_RUN_DEFAULT); } ``` 線程函數fbi()將會在不同的線程中運行,傳入`uv_work_t`結構體參數,一旦fib()函數返回,after_fib()會被event loop中的線程調用,然后被傳入同樣的結構體。 為了封裝阻塞的庫,常見的模式是用[baton](http://nikhilm.github.io/uvbook/utilities.html#baton)來交換數據。 從libuv 0.9.4版后,添加了函數`uv_cancel()`。它可以用來取消工作隊列中的任務。只有還未開始的任務可以被取消,如果任務已經開始執行或者已經執行完畢,`uv_cancel()`調用會失敗。 當用戶想要終止程序的時候,`uv_cancel()`可以用來清理任務隊列中的等待執行的任務。例如,一個音樂播放器可以以歌手的名字對歌曲進行排序,如果這個時候用戶想要退出這個程序,`uv_cancel()`就可以做到快速退出,而不用等待執行完任務隊列后,再退出。 讓我們對上述程序做一些修改,用來演示`uv_cancel()`的用法。首先讓我們注冊一個處理中斷的函數。 ####queue-cancel/main.c ```c int main() { loop = uv_default_loop(); int data[FIB_UNTIL]; int i; for (i = 0; i < FIB_UNTIL; i++) { data[i] = i; fib_reqs[i].data = (void *) &data[i]; uv_queue_work(loop, &fib_reqs[i], fib, after_fib); } uv_signal_t sig; uv_signal_init(loop, &sig); uv_signal_start(&sig, signal_handler, SIGINT); return uv_run(loop, UV_RUN_DEFAULT); } ``` 當用戶通過`Ctrl+C`觸發信號時,`uv_cancel()`回收任務隊列中所有的任務,如果任務已經開始執行或者執行完畢,`uv_cancel()`返回0。 ####queue-cancel/main.c ```c void signal_handler(uv_signal_t *req, int signum) { printf("Signal received!\n"); int i; for (i = 0; i < FIB_UNTIL; i++) { uv_cancel((uv_req_t*) &fib_reqs[i]); } uv_signal_stop(req); } ``` 對于已經成功取消的任務,他的回調函數的參數`status`會被設置為`UV_ECANCELED`。 ####queue-cancel/main.c ```c void after_fib(uv_work_t *req, int status) { if (status == UV_ECANCELED) fprintf(stderr, "Calculation of %d cancelled.\n", *(int *) req->data); } ``` `uv_cancel()`函數同樣可以用在`uv_fs_t`和`uv_getaddrinfo_t`請求上。對于一系列的文件系統操作函數來說,`uv_fs_t.errorno`會同樣被設置為`UV_ECANCELED`。 #####Tip >一個良好設計的程序,應該能夠終止一個已經開始運行的長耗時任務。 Such a worker could periodically check for a variable that only the main process sets to signal termination. ##<a name="inter_thread_communication-pane"></a>Inter-thread communication 很多時候,你希望正在運行的線程之間能夠相互發送消息。例如你在運行一個持續時間長的任務(可能使用uv_queue_work),但是你需要在主線程中監視它的進度情況。下面有一個簡單的例子,演示了一個下載管理程序向用戶展示各個下載線程的進度。 ####progress/main.c ```c 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); } ``` 因為異步的線程通信是基于event-loop的,所以盡管所有的線程都可以是發送方,但是只有在event-loop上的線程可以是接收方(或者說event-loop是接收方)。在上述的代碼中,當異步監視者接收到信號的時候,libuv會激發回調函數(print_progress)。 #####WARNING >應該注意: 因為消息的發送是異步的,當`uv_async_send`在另外一個線程中被調用后,回調函數可能會立即被調用, 也可能在稍后的某個時刻被調用。 libuv也有可能多次調用`uv_async_send`,但只調用了一次回調函數。唯一可以保證的是: 線程在調用`uv_async_send`之后回調函數可至少被調用一次。 如果你沒有未調用的`uv_async_send`, 那么回調函數也不會被調用。 如果你調用了兩次(以上)的`uv_async_send`, 而 libuv 暫時還沒有機會運行回調函數, 則libuv可能會在多次調用`uv_async_send`后只調用一次回調函數,你的回調函數絕對不會在一次事件中被調用兩次(或多次)。 ####progress/main.c ```c 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; } } ``` 在上述的下載函數中,我們修改了進度顯示器,使用`uv_async_send`發送進度信息。要記住:`uv_async_send`同樣是非阻塞的,調用后會立即返回。 ####progress/main.c ```c void print_progress(uv_async_t *handle) { double percentage = *((double*) handle->data); fprintf(stderr, "Downloaded %.2f%%\n", percentage); } ``` 函數`print_progress`是標準的libuv模式,從監視器中抽取數據。 最后最重要的是把監視器回收。 ####progress/main.c ```c void after(uv_work_t *req, int status) { fprintf(stderr, "Download complete\n"); uv_close((uv_handle_t*) &async, NULL); } ``` 在例子的最后,我們要說下`data`域的濫用,[bnoordhuis](https://github.com/bnoordhuis)指出使用`data`域可能會存在線程安全問題,`uv_async_send()`事實上只是喚醒了event-loop。可以使用互斥量或者讀寫鎖來保證執行順序的正確性。 #####Note >互斥量和讀寫鎖不能在信號處理函數中正確工作,但是`uv_async_send`可以。 一種需要使用`uv_async_send`的場景是,當調用需要線程交互的庫時。例如,舉一個在node.js中V8引擎的例子,上下文和對象都是與v8引擎的線程綁定的,從另一個線程中直接向v8請求數據會導致返回不確定的結果。但是,考慮到現在很多nodejs的模塊都是和第三方庫綁定的,可以像下面一樣,解決這個問題: >1.在node中,第三方庫會建立javascript的回調函數,以便回調函數被調用時,能夠返回更多的信息。 ```javascript var lib = require('lib'); lib.on_progress(function() { console.log("Progress"); }); lib.do(); // do other stuff ``` >2.`lib.do`應該是非阻塞的,但是第三方庫卻是阻塞的,所以需要調用`uv_queue_work`函數。 3.在另外一個線程中完成任務想要調用progress的回調函數,但是不能直接與v8通信,所以需要`uv_async_send`函數。 4.在主線程(v8線程)中調用的異步回調函數,會在v8的配合下執行javscript的回調函數。(也就是說,主線程會調用回調函數,并且提供v8解析javascript的功能,以便其完成任務)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看