Linux多線程學習總結

  線程是程序中完成一個獨立任務的完整執行序列,即一個可調度的實體;進程至關於運行中程序的一種抽象。根據運行環境的調度者的身份,線程可分爲內核線程和用戶線程。內核線程,在有的系統上稱爲LWP(Light Weight Process,輕量級線程),運行在內核空間,由內核調度;用戶線程運行在用戶空間,由線程庫來調度。當進程的一個內核線程得到CPU的使用權時,它就加載並運行一個用戶線程。可見,內核線程至關於用戶線程運行的‘容器’,一個進程能夠擁有M個內核線程和N個用戶線程,其中M<=N,而且一個系統的全部進程中,M和N的比值是固定的。git

線程控制函數

pthread_creategithub

#include <pthread.h>
int pthread_create(pthread_t * tidp, const pthread_attr_t *attr, void *(*start_rtn)(void *), void *arg);
    // 返回:成功返回0,出錯返回錯誤編號

  當pthread_create函數返回成功時,有tidp指向的內存被設置爲新建立線程的線程ID,其類型pthread_t定義爲:編程

#include <bits/pthreadtypes.h>
typedef unsigned long int pthread_t;

  attr參數用於設置各類不一樣的線程屬性,爲NULL時表示默認線程屬性。新建立的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針的參數arg,若是須要向start_rtn函數傳入的參數不止一個,能夠把參數放入到一個結構中,而後把這個結構的地址做爲arg的參數傳入。安全

  線程建立時並不能保證哪一個線程會先運行:是新建立的線程仍是調用線程。新建立的線程能夠訪問調用進程的地址空間,而且繼承調用線程的浮點環境和信號屏蔽字,可是該線程的未決信號集被清除。那什麼是未決信號呢,信號產生到信號被處理這段時間間隔,稱信號是未決的。數據結構

pthread_exit架構

#include <pthread.h>
void pthread_exit(void *rval_ptr);
    // 線程終止

  線程在結束時最好調用該函數,以確保安全、乾淨的退出。pthread_exit函數經過rval_ptr參數向調用線程的回收者傳遞退出信息,進程中的其餘線程能夠調用pthread_join函數訪問到這個指針。pthread_exit執行完後不會返回到調用者,並且永遠不會失敗。函數

線程能夠經過如下三種方式退出,在不終止整個進程的狀況下中止它的控制流:oop

  • 線程只是從啓動過程當中退出,返回值是線程的退出碼
  • 線程能夠被同一進程中的其餘線程取消
  • 線程調用pthread_exit

pthread_join測試

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
    // 返回:成功返回0,出錯返回錯誤代碼

  thread是目標線程標識符,rval_ptr指向目標線程返回時的退出信息,該函數會一直阻塞,直到被回收的線程結束爲止。可能的錯誤碼爲:spa

pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);
    // 返回:成功返回0,出錯返回錯誤代碼

  默認狀況下,pthread_cancel函數會使有thread標識的線程的表現爲如同調用了參數爲PTHREAD_CANCEL的pthread_exit函數,可是,接收到取消請求的目標線程能夠決定是否容許被取消以及如何取消,這分別由如下兩個函數來控制:

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldstate);

  注意pthread_cancel並不等待線程結束,它只是提出請求。

 

互斥量

  互斥量本質是一把鎖,在訪問公共資源前對互斥量設置(加鎖),確保同一時間只有一個線程訪問數據,在訪問完成後再釋放(解鎖)互斥量。在互斥量加鎖以後,其餘線程試圖對該互斥量再次加鎖時都會被阻塞,知道當前線程釋放互斥鎖。若是釋放互斥量時有一個以上的互斥量,那麼全部在該互斥量上阻塞的線程都會變成可運行狀態,第一個變成運行的線程能夠對互斥量加鎖,其餘線程看到互斥量依然是鎖着的,只能再次阻塞等待該互斥量。

  互斥量用pthread_mutex_t數據類型表示,在使用互斥量以前,必須使用pthread_mutex_init函數對它進行初始化,注意,使用完畢後需調用pthread_mutex_destroy。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
    // 兩個函數返回值,成功返回0,不然返回錯誤碼

  pthread_mutex_init用於初始化互斥鎖,mutexattr用於指定互斥鎖的屬性,若爲NULL,則表示默認屬性。除了用這個函數初始化互斥所外,還能夠用以下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER。
  pthread_mutex_destroy用於銷燬互斥鎖,以釋放佔用的內核資源,銷燬一個已經加鎖的互斥鎖將致使不可預期的後果。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
    // 成功返回0,不然返回錯誤碼

  pthread_mutex_lock以原子操做給一個互斥鎖加鎖。若是目標互斥鎖已經被加鎖,則pthread_mutex_lock則被阻塞,直到該互斥鎖佔有者把它給解鎖。
  pthread_mutex_trylock和pthread_mutex_lock相似,不過它始終當即返回,而不論被操做的互斥鎖是否加鎖,是pthread_mutex_lock的非阻塞版本。當目標互斥鎖未被加鎖時,pthread_mutex_trylock進行加鎖操做;不然將返回EBUSY錯誤碼。注意:這裏討論的pthread_mutex_lock和pthread_mutex_trylock是針對普通鎖而言的,對於其餘類型的鎖,這兩個加鎖函數會有不一樣的行爲。
  pthread_mutex_unlock以原子操做方式給一個互斥鎖進行解鎖操做。若是此時有其餘線程正在等待這個互斥鎖,則這些線程中的一個將得到它。

互斥鎖使用示例:

/**
 * 使用3個線程分別打印 A B C
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

pthread_mutex_t g_mutex;
int g_cnt = 0;

void *func(void *arg)
{
    int loop = 3;
    int result = (int)arg;

    while (loop > 0) {
        if (g_cnt % 3 == result) {
            switch (result)
            {
                case 0: {
                    printf("--- a\n");
                    break;
                }
                case 1: {
                    printf("--- b\n");
                    break;
                }
                case 2: {
                    printf("--- c\n");
                    break;
                }
                default: {
                    return NULL;
                }
            }

            pthread_mutex_lock(&g_mutex);
            g_cnt++;
            loop--;
            pthread_mutex_unlock(&g_mutex);
        }
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;

    pthread_mutex_init(&g_mutex, NULL);

    pthread_create(&t1, NULL, func, (void *)0);
    pthread_create(&t2, NULL, func, (void *)1);
    pthread_create(&t3, NULL, func, (void *)2);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    return 0;
}

 

讀寫鎖

  讀寫鎖和互斥體相似,不過讀寫鎖有更高的並行性,互斥體要麼是鎖住狀態,要麼是不加鎖狀態,並且一次只有一個線程能夠對其加鎖。而讀寫鎖能夠有3個狀態,讀模式下鎖住狀態,寫模式下鎖住狀態,不加鎖狀態。一次只有一個線程能夠佔有寫模式的讀寫鎖,可是多個線程能夠同時佔用讀模式的讀寫鎖。讀寫鎖適合對數據結構讀的次數遠大於寫的狀況。

  當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖以前,全部試圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖是讀加鎖狀態時,全部試圖以讀模式對它進行加鎖的線程均可以獲得訪問權,可是任何但願以寫模式對此鎖進行加鎖的線程都會阻塞,直到全部的線程釋放它們的讀鎖爲止。

#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockaddr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);
    // 成功返回0,不然返回錯誤碼

  經過pthread_rwlock_init初始化讀寫鎖,若是但願讀寫鎖有默認屬性,能夠傳一個NULL指針給attr。當再也不須要讀寫鎖時,調用pthread_rwlock_destroy作清理工做。

#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock);
    // 成功返回0,不然返回錯誤碼

  讀寫鎖的讀加鎖、寫加鎖和解鎖操做。

讀寫鎖程序示例:

/**
 * 兩個讀線程讀取數據,一個寫線程更新數據
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define READ_THREAD  0
#define WRITE_THREAD 1

int g_data = 0;
pthread_rwlock_t g_rwlock;

void *func(void *pdata)
{
    int data = (int)pdata;

    while (1) {
        if (READ_THREAD == data) {
            pthread_rwlock_rdlock(&g_rwlock);
            printf("-----%d------ %d\n", pthread_self(), g_data);
            sleep(1);
            pthread_rwlock_unlock(&g_rwlock);
            sleep(1);
        }
        else {
            pthread_rwlock_wrlock(&g_rwlock);
            g_data++;
            printf("add the g_data\n");
            pthread_rwlock_unlock(&g_rwlock);
            sleep(1);
        }
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;

    pthread_rwlock_init(&g_rwlock, NULL);

    pthread_create(&t1, NULL, func, (void *)READ_THREAD);
    pthread_create(&t2, NULL, func, (void *)READ_THREAD);
    pthread_create(&t3, NULL, func, (void *)WRITE_THREAD);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    pthread_rwlock_destroy(&g_rwlock);

    return 0;
}

 

條件變量

  條件變量是線程可用的一種同步機制,條件變量給多個線程提供了一個回合的場所,條件變量和互斥量一塊兒使用,容許線程以無競爭的方式等待特定的條件發生。條件變量本事是由互斥體保護的,線程在改變條件狀態以前必須首先鎖住互斥量,其餘線程在獲取互斥量以前就不會覺察到這種變化,由於互斥量必須鎖定以後才改變條件。

#include<pthread.h>
pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_destroy(pthread_cont_t *cond);
    // 成功返回0,不然返回錯誤碼

  使用條件變量前調用pthread_cond_init初始化,使用完畢後調用pthread_cond_destroy作清理工做。除非須要建立一個具備非默認屬性的條件變量,不然pthread_cond_init函數的attr參數能夠設置爲NULL。

#include<pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    // 成功返回0,不然返回錯誤碼

  傳遞給pthread_cond_wait的互斥量對條件進行保護,調用者把鎖住互斥量傳給函數,函數而後自動把調用線程放到等待條件的線程列表上,對互斥量解鎖。這就關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操做之間的時間通道,這樣線程就不會錯過條件的任何變化。pthread_cond_wait函數返回時,互斥量再次被鎖住。

  pthread_cond_broadcast用廣播的形式喚醒全部等待條件變量的線程。pthread_cond_signal用於喚醒一個等待條件變量的線程,至於哪一個線程被喚醒,取決於線程的優先級和調度機制。有時候須要喚醒一個指定的線程,但pthread沒有對該須要提供解決方法。能夠間接實現該需求:定義一個可以惟一表示目標線程的全局變量,在喚醒等待條件變量的線程前先設置該變量爲目標線程,而後以廣播形式喚醒全部等待條件變量的線程,這些線程被喚醒後都檢查改變量是不是本身,若是是就開始執行後續代碼,不然繼續等待。

條件變量程序示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define err_sys(msg) \
    do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
    do { fprintf(stderr, msg); exit(-1); } while(0)

pthread_cond_t cond;

void *r1(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;

    while(cnt--)
    {
        printf("r1: I am wait.\n");
        pthread_mutex_lock(mutex);
        pthread_cond_wait(&cond, mutex); /* mutex參數用來保護條件變量的互斥鎖,調用pthread_cond_wait前mutex必須加鎖 */
        pthread_mutex_unlock(mutex);
    }
    return "r1 over";
}

void *r2(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;

    while(cnt--)
    {
        //pthread_mutex_lock(mutex); //這個地方不用加鎖操做就行
        printf("r2: I am send the cond signal.\n");
        pthread_cond_signal(&cond);
        //pthread_mutex_unlock(mutex);
        sleep(1);
    }
    return "r2 over";
}

int main(void)
{
    pthread_mutex_t mutex;
    pthread_t t1, t2;
    char* p1 = NULL;
    char* p2 = NULL;
    
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&t1, NULL, r1, &mutex);
    pthread_create(&t2, NULL, r2, &mutex);

    pthread_join(t1, (void **)&p1);
    pthread_join(t2, (void **)&p2);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
    printf("s1: %s\n", p1);
    printf("s2: %s\n", p2);

    return 0;
}

 

自旋鎖

  自旋鎖和互斥量相似,但它不是經過休眠使進程阻塞,而是在獲取鎖以前一直處於忙等(自旋)狀態,自旋鎖可用於下面的狀況:鎖被持有的時間短,而且線程不但願再從新調度上花費太多的成本。自旋鎖一般做爲底層原語用於實現其餘類型的鎖。根據他們所基於的系統架構,能夠經過使用測試並設置指令有效地實現。固然這裏說的有效也仍是會致使CPU資源的浪費:當線程自旋鎖變爲可用時,CPU不能作其餘任何事情,這也是自旋鎖只可以被只有一小段時間的緣由。

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

  pshared參數表示進程共享屬性,代表自旋鎖是如何獲取的,若是它設爲PTHREAD_PROCESS_SHARED,則自旋鎖能被能夠訪問鎖底層內存的線程所獲取,即便那些線程屬於不一樣的進程。不然pshared參數設爲PTHREAD_PROCESS_PROVATE,自旋鎖就只能被初始化該鎖的進程內部的線程訪問到。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

  若是自旋鎖當前在解鎖狀態,pthread_spin_lock函數不要自旋就能夠對它加鎖,試圖對沒有加鎖的自旋鎖進行解鎖,結果是未定義的。須要注意,不要在持有自旋鎖狀況下可能會進入休眠狀態的函數,若是調用了這些函數,會浪費CPU資源,其餘線程須要獲取自旋鎖須要等待的時間更長了。

自旋鎖使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_spinlock_t g_lock;
int g_data = 0;

void *func(void *arg)
{
    while (1) {
        pthread_spin_lock(&g_lock);
        g_data++;
        printf("----------- %d\n", g_data);
        sleep(1);
        pthread_spin_unlock(&g_lock);
    }
}

int main(int argc, char **argv)
{
    pthread_t tid;
    pthread_spin_init(&g_lock, PTHREAD_PROCESS_PRIVATE);

    pthread_create(&tid, NULL, func, NULL);
    pthread_create(&tid, NULL, func, NULL);
    pthread_create(&tid, NULL, func, NULL);

    pthread_join(tid, NULL);

    return 0;
}

 

屏障

  屏障是用戶協調多個線程並行工做的同步機制,屏障容許每一個線程等待,直到全部合做的線程都到達某一點,而後從該點出繼續執行。pthread_join其實就是一種屏障,容許一個線程等待,直到另外一個線程退出。可是屏障對象的概念更廣,它們容許任意數量的線程等待,直到全部的線程完成處理工做,而線程不須要退出,全部線程達到屏障後能夠繼續工做。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
    // 成功返回0,不然返回錯誤編號

  初始化屏障時,可使用count參數指定,在容許全部線程繼續運行前,必須達到屏障的線程數目。attr指定屏障屬性,NULL爲默認屬性。

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
    // 成功返回0,不然返回錯誤編號

  可使用pthread_barrier_wait函數來代表,線程已完成工做,準備等全部其餘線程趕過來。調用pthread_barrier_wait的線程在屏障計數未知足條件時,會進入休眠狀態。若是該線程是最後一個調用pthread_barrier_wait的線程,則全部的線程會被喚醒。

  一旦到達屏障計數值,並且線程處於非阻塞狀態,屏障就能夠被重複使用。

屏障使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_barrier_t g_barrier;

void *func(void *arg)
{
    int id = (int )arg;

    if (id == 0) {
        printf("thread 0\n");
        sleep(1);
        pthread_barrier_wait(&g_barrier);
        printf("thread 0 come...\n");
    }
    else if (id == 1) {
        printf("thread 1\n");
        sleep(2);
        pthread_barrier_wait(&g_barrier);
        printf("thread 1 come...\n");    
    }
    else if (id == 2) {
        printf("thread 2\n");
        sleep(3);
        pthread_barrier_wait(&g_barrier);
        printf("thread 2 come...\n");
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;
    pthread_barrier_init(&g_barrier, NULL, 3);

    pthread_create(&t1, NULL, func, (void *)0);
    pthread_create(&t2, NULL, func, (void *)1);
    pthread_create(&t3, NULL, func, (void *)2);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    return 0;
}

 

參考:

  一、《UNIX環境高級編程 第三版》線程章節

  二、ThinkInTechnology

相關文章
相關標籤/搜索