【libuv高效編程】libuv學習超詳細教程7——libuv線程解讀

libuv系列文章

linux線程

從很多Linux的書籍我們都可以這樣子描述進程和線程的:進程是資源管理的最小單位,線程是程序執行的最小單位。

libuv是的底層其實有使用線程池對多個線程進行管理,而且它也提供了用戶創建線程的功能,今天我們來學習一下libuv線程相關的知識。

線程是操作系統能夠調度和執行的基本單位,在Linux中也被稱之爲輕量級進程。在Linux系統中,一個進程至少需要一個線程作爲它的指令執行體,進程管理着資源(比如cpu、內存、文件等等),而將線程分配到某個cpu上執行。一個進程可以擁有多個線程,它還可以同時使用多個cpu來執行各個線程,以達到最大程度的並行,提高工作的效率;同時,即使是在單cpu的機器上,也依然可以採用多線程模型來設計程序,使設計更簡潔、功能更完備,程序的執行效率也更高。

從上面的這些概念我們不難得出一個非常重要的結論:線程的本質是一個進程內部的一個控制序列,它是進程裏面的東西,一個進程可以擁有一個線程或者多個線程。

POSIX

我們可以先了解一個標準:可移植操作系統接口(英語:Portable Operating System Interface,縮寫爲POSIX),POSIX是IEEE爲要在各種UNIX操作系統上運行軟件,而定義API的一系列互相關聯的標準的總稱,其正式稱呼爲IEEE Std 1003,而國際標準名稱爲ISO/IEC 9945。此標準源於一個大約開始於1985年的項目。POSIX這個名稱是由理查德·斯托曼(RMS)應IEEE的要求而提議的一個易於記憶的名稱。它基本上是Portable Operating System Interface(可移植操作系統接口)的縮寫,而X則表明其對Unix API的傳承。

注:以上介紹來自維基百科:https://zh.wikipedia.org/wiki/POSIX

在Linux系統下的多線程遵循POSIX標準,而其中的一套常用的線程庫是 pthread ,它是一套通用的線程庫,是由 POSIX 提出的,因此具有很好的可移植性,我們學習多線程編程,就是使用它,必須包含以下頭文件:

#include <pthread.h>

除此之外在鏈接時需要使用庫libpthread.a。因爲pthread的庫不是Linux系統的庫,所以在編譯時要加上 -lpthread 選項。

創建線程

pthread_create()函數是用於創建一個線程的,創建線程實際上就是確定調用該線程函數的入口點,在線程創建後,就開始運行相關的線程函數。若線程創建成功,則返回0。若線程創建失敗,則返回對應的錯誤代碼。

函數原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);

參數:

  • thread:指向線程標識符的指針。
  • attr:設置線程屬性。
  • start_routine:start_routine是一個函數指針,指向要運行的線程入口。
  • arg:運行線程時傳入的參數。

線程的分離狀態

什麼是線程的分離狀態呢?在任何一個時間點上,線程是可結合的(joinable),或者是分離的(detached)。一個可結合的線程能夠被其他線程收回其資源和殺死;在被其他線程回收之前,它的存儲器資源(如棧)是不釋放的。相反,一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。

總而言之:線程的分離狀態決定一個線程以什麼樣的方式來終止自己。

進程中的線程可以調用以下函數來等待某個線程的終止,獲得該線程的終止狀態,並收回所佔的資源,如果對線程的返回狀態不感興趣,可以將rval_ptr設置爲NULL。

int pthread_join(pthread_t tid, void **rval_ptr)

除此之外線程也可以調用以下函數將此線程設置爲分離狀態,設置爲分離狀態的線程在線程結束時,操作系統會自動收回它所佔的資源。設置爲分離狀態的線程,不能再調用pthread_join()等待其結束。

int pthread_detach(pthread_t tid)

如果一個線程是可結合的,意味着這條線程在退出時不會自動釋放自身資源,而會成爲殭屍線程,同時意味着該線程的退出值可以被其他線程獲取。因此,如果不需要某條線程的退出值的話,那麼最好將線程設置爲分離狀態,以保證該線程不會成爲殭屍線程。

如果在創建線程時就知道不需要了解線程的終止狀態,那麼可以通過修改pthread_attr_t結構中的detachstate屬性,讓線程以分離狀態啓動,調用函數如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

如果想要獲取某個線程的分離狀態,那麼可以通過以下函數:

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

若函數調用成功返回0,否則返回對應的錯誤代碼。

參數:

  • attr:指向一個線程屬性的指針。
  • detachstate:如果值爲PTHREAD_CREATE_DETACHED,則表示線程是分離狀態,如果值爲PTHREAD_CREATE_JOINABLE則表示線程是結合狀態。

其實linux線程庫中還有其他的api函數,這裏就不做過多的贅述。

libuv的線程處理

因爲libuv是一個跨平臺的框架,它的底層處理可以在Windows、可以在linux,所以線程的實現它也是視平臺而定的,在這裏我們只講解linux平臺下的處理,當然對應的Windows也是差不多的。

線程也是有生命週期的,可以把線程當做一個handle,那麼libuv的線程就是thread handle。

數據類型

uv_thread_t 是 thread handle 的數據類型,通過它可以定義一個 thread handle 的實例。

uv_thread_t

github/libuv/include/uv/unix.h文件中存在以下的定義,它就是linux下線程的數據結構,實際上就是使用了linux平臺本身的線程庫 pthread

typedef pthread_t uv_thread_t;

線程主體

既然能創建線程就必須有一個線程主體去執行線程裏面的工作,在linux線程庫 pthread 中定義的線程主體是void *(*start_routine) (void *)類型,而libuv則是以下類型:

void (*entry)(void *arg)
  • 傳入了一個參數arg,它由用戶創建線程時指定。

libuv創建線程

因爲線程是一個handle,所以在使用的時候也是需要創建的,libuv創建線程的函數是uv_thread_create()

int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg);

傳入的參數:

  • tid:線程句柄
  • entry:線程主體。
  • arg:線程參數。

源碼的實現如下:

int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) {
  uv_thread_options_t params;
  params.flags = UV_THREAD_NO_FLAGS;
  return uv_thread_create_ex(tid, &params, entry, arg);
}

/* 這纔是真正創建線程的地方 */
int uv_thread_create_ex(uv_thread_t* tid,
                        const uv_thread_options_t* params,
                        void (*entry)(void *arg),
                        void *arg) {
  int err;
  pthread_attr_t* attr;
  pthread_attr_t attr_storage;
  size_t pagesize;
  size_t stack_size;

  /* Used to squelch a -Wcast-function-type warning. */
  union {
    void (*in)(void*);
    void* (*out)(void*);
  } f;

  stack_size =
      params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0;

  attr = NULL;
  if (stack_size == 0) {
    stack_size = thread_stack_size();
  } else {
    pagesize = (size_t)getpagesize();
    /* Round up to the nearest page boundary. */
    stack_size = (stack_size + pagesize - 1) &~ (pagesize - 1);
#ifdef PTHREAD_STACK_MIN
    if (stack_size < PTHREAD_STACK_MIN)
      stack_size = PTHREAD_STACK_MIN;
#endif
  }

  if (stack_size > 0) {
    attr = &attr_storage;

    if (pthread_attr_init(attr))
      abort();

    if (pthread_attr_setstacksize(attr, stack_size))
      abort();
  }

  /* 最終調用線程庫 pthread 的pthread_create()函數創建線程*/
  f.in = entry;
  err = pthread_create(tid, attr, f.out, arg);

  if (attr != NULL)
    pthread_attr_destroy(attr);

  return UV__ERR(err);
}

libuv的線程分離狀態

其實這個就非常簡單了,直接是依賴linux的線程庫的函數,具體見:

int uv_thread_join(uv_thread_t *tid) {
  return UV__ERR(pthread_join(*tid, NULL));
}

libuv線程的其他API

這裏面的API很多都是依賴linux的線程庫的函數,我就簡單列舉一下:

  • 獲取線程句柄:
uv_thread_t uv_thread_self(void) {
  return pthread_self();
}
  • 判斷線程是否相同:
int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2) {
  return pthread_equal(*t1, *t2);
}

當然啦,其實還有很多互斥鎖、信號量相關的實現都是差不多的,說白了就是在操作系統之上抽象了一個操作系統模擬層。

example

本章的學習是下一章的基礎,就簡單用一個龜兔賽跑的故事來實現兩個線程吧,在例程中創建兩個線程句柄,分別爲tortoise、hare,他們在長度固定的跑到比賽,假設爲10米,烏龜慢一點,3秒跑一米,兔子快一點,1秒跑一米,代碼的實現如下:

#include <stdio.h>
#include <unistd.h>

#include <uv.h>

void hare_entry(void *arg) 
{
    int track_len = *((int *) arg);
    while (track_len) {
        track_len--;
        sleep(1);
        printf("hare ran another step\n");
    }
    printf("hare done running!\n");
}

void tortoise_entry(void *arg) 
{
    int track_len = *((int *) arg);
    while (track_len) 
    {
        track_len--;
        printf("tortoise ran another step\n");
        sleep(3);
    }
    printf("tortoise done running!\n");
}

int main() {
    int track_len = 10;
    uv_thread_t hare;
    uv_thread_t tortoise;
    uv_thread_create(&hare, hare_entry, &track_len);
    uv_thread_create(&tortoise, tortoise_entry, &track_len);

    uv_thread_join(&hare);
    uv_thread_join(&tortoise);
    return 0;
}

編譯運行後的結果如下:

tortoise ran another step
hare ran another step
hare ran another step
tortoise ran another step
hare ran another step
hare ran another step
hare ran another step
tortoise ran another step
hare ran another step
hare ran another step
hare ran another step
tortoise ran another step
hare ran another step
hare ran another step
hare done running!
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise done running!

參考

libuv官方文檔

例程代碼獲取

libuv-learning-code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章