1. 程式人生 > >linux/unix多執行緒/多程序程式設計總結(一)

linux/unix多執行緒/多程序程式設計總結(一)

linux/unix多執行緒,多程序程式設計是在實際工作中經常使用到的技能,在C語言或者C++語言面試的時候也經常會被問到此部分內容。
本文對linux/unix系統中的pthread相關的多程序和多執行緒程式設計的各個方面進行了總結,包括執行緒、程序、程序間通訊及執行緒互斥等內容。一方面,給感興趣的同事瀏覽斧正;另一方面,也是自己的一個技術筆記,方便以後回顧。

多執行緒

pthread_t

  1. 執行緒結構體,用於儲存執行緒id。
  2. 定義在檔案/usr/include/bits/pthreadtypes.h中,定義如下:
    typedef unsigned long int pthread_t;  //pthread_t是一個無符號長整型。

pthread_attr_t

pthread_create

  1. pthread_create用於建立一個執行緒,函式原型為:
pthread_create (
    pthread_t * tid,  //執行緒id結構體
    const pthread_attr_t * attr, //執行緒屬性結構體
    void * ( * start )(void *), //執行緒函式指標,返回是void *,引數也是void *
    void * arg); //傳遞給執行緒函式的引數
    //返回值:
    //    成功:
    //        返回0;
    //    失敗:
// 返回error number。 // EAGAIN: 達到了系統資源限制。 // EINVAL: attr設定無效。 // EPERM: 在attr屬性中修改的設定沒有許可權。
  1. 通過pthread_create()建立的執行緒可以以以下三種方式退出:
    1. 呼叫pthread_exit( void * retval ),其中retval會返回給同一程序中呼叫了pthread_join(pthread_t thread, void **retval)的執行緒。即pthread_exit中的retval會被傳遞到void **retval指向的空間。
    2. 執行緒函式正常return,那麼return的值也會被傳遞到pthread_join()中的void ** retval指向的空間。
    3. 執行緒被pthread_cancel(pthread_t thread)呼叫取消。當被取消的執行緒結束了(terminated),那麼pthread_join()這個執行緒的執行緒的void ** retval會被設定成PTHREAD_CANCELED。

程式碼一:

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

void * test( void * par) {
        int loop = 5,i = 0;
        for(i = 0;i <5;i++) {
                sleep(1);
                printf("執行緒id是:%d\n", pthread_self()); //獲得執行緒自身id。
        }
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t; //執行緒結構體
        pthread_create(&t, NULL, test, NULL); //建立執行緒
        pthread_join(t,NULL); //等待執行緒執行結束(如果不加此句,那麼新執行緒中test函式還沒有執行結束主執行緒就退出了,
                              //而主執行緒退出會導致整個程序退出,因此如果沒有此句,test()不能執行完成。)
        return 0;
}

程式碼二:(向執行緒函式傳遞引數)

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

typedef struct {
        int a;
        int b;
        int c;
        int d;
} A;

void * test( void * par) {
        A * p = (A *)par; //型別轉換
        printf ("%d %d %d %d\n",p->a, p->b, p->c, p->d); //使用執行緒引數
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t;
        A par;

        par.a = 1; par.b = 2; par.c = 3; par.d = 4; //執行緒引數
        pthread_create(&t, NULL, test, (void *)&par); //傳遞執行緒引數
        pthread_join(t,NULL);
        return 0;
}

pthread_join

  1. 呼叫pthread_join的函式會阻塞在pthread_join函式的呼叫處,直到pthread_join等待的執行緒結束,呼叫執行緒才會繼續從pthread_join處執行。見程式碼一。
  2. 函式原型:
    int pthread_join( pthread_t thread, //執行緒id。 
                      void ** retval ); //儲存執行緒返回值的空間的地址。
    //返回值:
    //    成功返回0,
    //    失敗返回error number.
    //        EDEADLK: 檢測到死鎖(兩個執行緒互相鎖定或者thread引數是本執行緒。即執行緒自己不能join自己,否則返回EDEADLK錯誤。)
    //        EINVAL: thread引數無效或者另外一個執行緒正在等待join這個thread。
    //        ESRCH: 查不到執行緒Id。
  1. pthread_join的操作裡包括了pthread_detach的行為,呼叫pthread_join會自動分離執行緒。
  2. 以下程式碼驗證:
    1. pthread_join如何獲得執行緒退出狀態。
    2. 驗證對於同一個執行緒不能同時多次pthread_join,否則程式崩潰。
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//對於同一個執行緒不能同時多次pthread_join,否則程式崩潰。
typedef struct {
        int a;
        int b;
} RET;

void * test(void * par) {
        RET * p = malloc(sizeof(RET));
        sleep(5);
        p->a = 10;
        p->b = 20;
        pthread_exit((void *)p);  //執行緒退出,並且返回退出狀態。
}

void * test1(void * par) {
        pthread_t t = *((pthread_t *)par);
        void * pRet = NULL; //用於儲存執行緒退出狀態指標
        printf("In test1, tid is %ld\n", t);
        pthread_join(t, &pRet); //獲得執行緒t的執行緒退出狀態。
        printf("Here.\n");
        printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b); //列印執行緒退出返回的值。
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t, t1;
        void * pRet;
        pthread_create(&t, NULL, test, NULL); //建立執行test的執行緒。
        printf("In main t tid is %ld\n", t);
        pthread_create(&t1, NULL, test1, &t); //建立執行test1的執行緒。
        pthread_join(t,&pRet); //此處join了t執行緒,在函式test1中也join了t執行緒。
        pthread_join(t1, NULL); 
        printf("ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
        return 0;
}
上面程式執行時發生崩潰,原因是對於執行緒t執行了兩次pthread_join函式。
[[email protected] pthread]# ./a.out 
In main t tid is 139668474418944
In test1, tid is 139668474418944
Here.
Segmentation fault (core dumped)

通過man pthread_join知道不能在多個執行緒中對同時對一個執行緒多次pthread_join.
man pthread_join結果如下:

DESCRIPTION
       The  pthread_join()  function  waits  for  the thread specified by thread to terminate.  If that thread has already terminated, then pthread_join()
       returns immediately.  The thread specified by thread must be joinable.
       (如果被join的執行緒已經結束,那麼phread_join立即返回。)

       If retval is not NULL, then pthread_join() copies the exit status of the target thread  (i.e.,  the  value  that  the  target  thread  supplied  to
       pthread_exit(3)) into the location pointed to by *retval.  If the target thread was canceled, then PTHREAD_CANCELED is placed in *retval.

       If  multiple threads simultaneously try to join with the same thread, the results are undefined.  If the thread calling pthread_join() is canceled,
       then the target thread will remain joinable (i.e., it will not be detached).
       (如果多個執行緒嘗試同時pthread_join同一個執行緒,那麼結果是未定義的。但是如果呼叫pthread_join()的執行緒退出了,那麼被join的執行緒仍然是joinable的,即可以被再次pthread_join。)

RETURN VALUE
       On success, pthread_join() returns 0; on error, it returns an error number.

ERRORS
       EDEADLK
              A deadlock was detected (e.g., two threads tried to join with each other); or thread specifies the calling thread.

       EINVAL thread is not a joinable thread.

       EINVAL Another thread is already waiting to join with this thread.

       ESRCH  No thread with the ID thread could be found.
    (此處說明了呼叫pthread_join最好檢查返回值,至少能夠在一定程度上避免死鎖的發生。)

NOTES
       After a successful call to pthread_join(), the caller is guaranteed that the target thread has terminated.

       Joining with a thread that has previously been joined results in undefined behavior.
    (join一個之前已經被joined執行緒會導致未定義的行為。)
       Failure to join with a thread that is joinable (i.e., one that is not detached), produces a "zombie thread".  Avoid doing this, since  each  zombie
       thread  consumes  some  system  resources, and when enough zombie threads have accumulated, it will no longer be possible to create new threads (or
       processes). (如果沒有對執行緒呼叫pthread_join,那麼會導致一個殭屍執行緒的出現,這樣會佔據系統資源,只有在程序退出的時候這個殭屍執行緒才被釋放。)

       There is no pthreads analog of waitpid(-1, &status, 0), that is, "join with any terminated thread".  If you believe you  need  this  functionality,
       you probably need to rethink your application design.

       All of the threads in a process are peers: any thread can join with any other thread in the process.
       (所有執行緒,包括主執行緒都是對等的,也就是說:任何執行緒都可以join所在程序的其他執行緒。)

如何修改上面崩潰了的程式?(方法:對同一個執行緒只調用一次pthread_join,要麼去掉test1中的pthread_join,要麼去掉main函式中的pthread_join。)
正確的程式如下:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//獲得執行緒返回值

typedef struct {
        int a;
        int b;
} RET;

void * test(void * par) {
        RET * p = malloc(sizeof(RET));
        sleep(5);
        p->a = 10;
        p->b = 20;
        pthread_exit((void *)p);
}

void * test1(void * par) {
    //等待test()所線上程退出,並獲取test()所線上程的返回值。
        pthread_t t = *((pthread_t *)par);
        void * pRet = NULL;
        printf("In test1, tid is %ld\n", t);
        pthread_join(t, &pRet); //對t只pthread_join了一次。
        printf("Here.\n");
        printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t, t1;
        pthread_create(&t, NULL, test, NULL);
        printf("In main t tid is %ld\n", t);
        pthread_create(&t1, NULL, test1, &t);
        pthread_join(t1, NULL);
        return 0;
}

- 執行緒的joinable和detached狀態。

  • 一個執行緒在生命週期中會在多個狀態之間進行轉換,joinable和detached是執行緒的兩種狀態,joinable可理解為“可匯合的”的意思(即執行緒最終要和主執行緒匯合),detached理解為“分離的”狀態。
  • 一個程序啟動之後會預設建立一個執行緒,這個執行緒叫做“主執行緒”,主執行緒接下來可能會再次建立多個執行緒,這些執行緒與主執行緒並行執行,主線建立的n個執行緒之間的關係是對等的,而主執行緒與從執行緒之間具有一定的管控關係(主執行緒結束則所有執行緒結束,整個程序結束。)。因為主執行緒與其他執行緒之間的這個特殊關係,一般在多執行緒程式中,都會在主執行緒中呼叫pthread_join()來匯合(連線)它建立的子執行緒,以回收從執行緒的資源。pthread_join()函式會阻塞主執行緒,直到其join的執行緒結束,pthread_join()才會繼續執行,這樣就避免了主執行緒先於從執行緒退出的問題。
  • 執行緒執行結束之後系統並不會回收其佔用的系統資源(例如執行緒id),所以如果不對joinable執行緒執行pthread_join,那麼另外一個隱患就是這些系統資源需要等到程序整個退出的時候才被釋放,這樣在多執行緒程式中就可能會引起資源不足問題(例如執行緒id達到最大限制),所以對於joinable的執行緒(執行緒預設都是joinable的)需要呼叫pthread_join()。
  • pthread_detach()函式可以對一個執行緒進行分離(意思就是與主執行緒分離,即脫離了主執行緒的管控),分離之後的執行緒處於detached狀態,detached的執行緒在結束之後其佔用的資源(例如執行緒控制代碼等)能夠立即被系統回收,而不需要pthread_join()來回收執行緒資源了。(pthread_detach()可以使得執行緒資源線上程結束之後立即被系統回收。)
  • 預設情況下執行緒都是joinable的,joinable可以通過pthread_detached()來進行分離,而分離了的執行緒不能再次被變成joinable的了。似的執行緒變為detached可以通過pthread_detach(),也可以線上程建立的時候設定執行緒屬性PTHREAD_CREATE_DETACHED使得執行緒創建出來就是detached狀態的。
  • 創建出來的執行緒也可以通過pthread_detach(pthread_self())來detach自己。
  • detach執行緒這種方式常用線上程數較多的情況,有時讓主執行緒逐個等待子執行緒結束,或者讓主執行緒安排每個子執行緒結束的等待順序,是很困難或者不可能的。所以在併發子執行緒較多的情況下,這種detach執行緒的方式也會經常使用。

pthread_detach

  1. 函式原型: int pthread_detach(pthread_t thread);
  2. 函式說明:
    1. 引數thread: 要從程序中分離(detach)中去的執行緒。
    2. 建立一個執行緒的預設狀態是joinable的,如果一個執行緒結束但是沒有被join,則它就成為了殭屍執行緒(一直佔用系統資源,但是執行緒已經不存在了,不做任何事情。直到程序結束資源才被回收。)。
    3. 在呼叫了pthread_join之後,如果執行緒已經結束了,那麼pthread_join會立即返回,如果執行緒還沒有結束,那麼呼叫pthread_join的執行緒會被阻塞。但是在有些時候我們並不希望呼叫pthread_join的執行緒被阻塞。在不希望執行緒被阻塞的場景,我們不能呼叫pthread_join,我們應該呼叫pthread_detach分離執行緒(pthread_detach告訴作業系統此執行緒已經從程序中分離出去了(此分離只是概念上的分離,變數等等並沒有真實的分離,執行緒還是屬於程序中的執行緒,只是在作業系統中做了標識,這個執行緒結束之後執行緒佔用的資源(執行緒堆疊等)可以直接被作業系統回收。))。
    4. 執行緒可以分離自己,也可以由別的執行緒分離。
      1. 執行緒分離自己:pthread_detach(pthread_self());
      2. 執行緒分離其他執行緒: pthread_detach( thread_id );
      3. 執行緒被分離後就不需要再被pthread_join了,執行緒結束之後作業系統就會自動回收執行緒的資源。
    5. 分離一個正在執行的執行緒不會對執行緒帶來任何影響,僅僅是通知作業系統當前執行緒結束的時候其所屬的資源可以被回收。沒有被分離的執行緒終止的時候會保留其虛擬記憶體,包括他們的堆疊和其他的系統資源。分離執行緒意味著告訴作業系統不再需要此資源,允許系統將分配給他們的資源回收。執行緒可以分離自己,任何獲得其ID的其他執行緒可以隨時分離它。

pthread_exit

  1. 從當前執行緒退出,並返回引數中的值給所有。
  2. 函式原型: int pthread_exit(void * value_ptr);
  3. 函式說明:
    1. 用來終止當前執行緒,並通過指標value_ptr返回程式狀態給呼叫pthread_join()函式等待此執行緒結束的執行緒(對同一個執行緒只能同時又一次有效的pthread_join,見pthread_join一節)。
    2. 可以在main函式中呼叫pthread_exit(0),這樣主執行緒(即程序)就必須等到所有執行緒結束才能終止。
      程式碼如下:
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//獲得執行緒返回值

typedef struct {
        int a;
        int b;
} RET;

void * test(void * par) {
        RET * p = malloc(sizeof(RET));
        sleep(5);
        p->a = 10;
        p->b = 20;
        pthread_exit((void *)p);
}

void * test1(void * par) {
        pthread_t t = *((pthread_t *)par);
        void * pRet = NULL;
        printf("In test1, tid is %ld\n", t);
        pthread_join(t, &pRet);
        printf("Here.\n");
        printf("Func test1. ret is %d, %d\n", ((RET *)pRet)->a, ((RET *)pRet)->b);
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t, t1;
        pthread_create(&t, NULL, test, NULL);
        pthread_create(&t1, NULL, test1, &t);
        //pthread_exit(0);
        return 0;
}
注意:要麼在主執行緒中pthread_join,要麼pthread_exit(0),否則當主執行緒執行完成,即使別的執行緒正在執行,那麼整個程序也會結束。如上例,如果不加pthread_exit(0),則不會有任何列印;如果加上pthread_exit(0),則程式正確執行。
[root@localhost pthread]# ./a.out 
In test1, tid is 140269881673472
Here.
Func test1. ret is 10, 20

pthread_self

獲得當前執行緒的id號,見程式碼一。
函式原型 pthread_t pthread_self( void );

pthread_equal

函式原型: int pthread_equal(pthread t1, pthread t2);
函式說明:
    判斷兩個執行緒的id是否相等,即傳遞過來的兩個執行緒是否是同一執行緒。
    t1 == t2,返回非0;
    t1 != t2,返回0。

驗證pthread_equal:

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

void * test(void * par) {
        sleep(1);
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t;
        pthread_create(&t, NULL, test, NULL);
        //if(pthread_equal(t,t) == 0) {
        if(pthread_equal(t, pthread_self()) == 0) {
                printf("threads are different: %ld %ld\n", t, pthread_self());
        } else {
                printf("threads are same: %ld %ld\n", t, pthread_self());
        }
        return 0;
}

pthread_attr_init/pthread_attr_destroy

  • 初始化執行緒屬性結構體,初始化後執行緒屬性值都是系統預設的執行緒屬性,如果不對執行緒屬性進行初始化,那麼屬性值不定,所以在使用的時候,如果要設定執行緒屬性,都要執行pthread_attr_init()把不需要修改的執行緒屬性設定為預設值。
  • 函式原型:
    • int pthread_attr_init(pthread_attr_t *attr); //初始化pthread_attr_t
    • int pthread_attr_destroy(pthread_attr_t *attr); //銷燬pthread_attr_t
  • man手冊說明:
    Calling pthread_attr_init() on a thread attributes object that has already been initialized results in undefined behavior. (對於已經初始化了的pthread_attr_t不應該再次被呼叫pthread_attr_init,否則會導致未定義行為,即手冊建議最好只調用一次。)

    When a thread attributes object is no longer required, it should be destroyed using the pthread_attr_destroy() function. Destroying a thread attributes object has no effect on threads that were created using that object. (對pthread_attr_t結構體的銷燬不會影響用這個屬性已經建立完成的執行緒。)

    Once a thread attributes object has been destroyed, it can be reinitialized using pthread_attr_init(). Any other use of a destroyed thread attributes object has undefined results. (pthread_attr_t被銷燬之後可以再次呼叫pthread_attr_init進行初始化,不初始化就使用已經銷燬了的attr會導致未定義行為。)

pthread_attr_setdetachstate/pthread_attr_getdetachstate

  • 獲得及設定執行緒的detach屬性。
  • 函式原型:
    • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    • int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
  • 兩個函式都是執行緒安全函式(man手冊)

pthread_cancel

  • 用於cancel一個執行緒,執行緒是否能夠被取消及何時被取消決定於執行緒的引數設定,具體參看以下man手冊。
  • pthread_cancel執行結束不代表被取消的執行緒已經終止了,應該通過pthread_join來獲得被取消執行緒的狀態即返回值。
DESCRIPTION
       The  pthread_cancel()  function  sends  a cancellation request to the thread thread.  Whether and when the target thread reacts to the cancellation
       request depends on two attributes that are under the control of that thread: its cancelability state and type.

       A thread's cancelability state, determined by pthread_setcancelstate(3), can be enabled (the default for new threads) or disabled.  If a thread has
       disabled  cancellation,  then  a  cancellation request remains queued until the thread enables cancellation.  If a thread has enabled cancellation,
       then its cancelability type determines when cancellation occurs.

       A thread's cancellation type, determined by pthread_setcanceltype(3), may be either asynchronous or deferred (the default for new threads).   Asyn‐
       chronous  cancelability  means that the thread can be canceled at any time (usually immediately, but the system does not guarantee this).  Deferred
       cancelability means that cancellation will be delayed until the thread next calls a function that is a cancellation point.   A  list  of  functions
       that are or may be cancellation points is provided in pthreads(7).

       When a cancellation requested is acted on, the following steps occur for thread (in this order):

       1. Cancellation clean-up handlers are popped (in the reverse of the order in which they were pushed) and called.  (See pthread_cleanup_push(3).)

       2. Thread-specific data destructors are called, in an unspecified order.  (See pthread_key_create(3).)

       3. The thread is terminated.  (See pthread_exit(3).)

       The  above  steps  happen asynchronously with respect to the pthread_cancel() call; the return status of pthread_cancel() merely informs the caller
       whether the cancellation request was successfully queued.

       After a canceled thread has terminated, a join with that thread using pthread_join(3) obtains PTHREAD_CANCELED as the thread's exit status.  (Join‐
       ing with a thread is the only way to know that cancellation has completed.)

執行緒間併發

互斥鎖mutex

  1. 互斥鎖一般都用在同一個程序內執行緒之間的互斥。通過修改mutex的共享屬性pthread_mutexattr_setpshared()也可以實現互斥鎖在多個程序之間共享,但是在實際使用中,這種用法並不普遍。原因如下:
    • 要想互斥鎖用於程序之間,那麼要把互斥鎖放在共享記憶體中,並且進行初始化,這就涉及到一個問題:不能多個程序都對互斥鎖進行初始化,因為這樣的話由於排程的關係,有可能有的程序完成了初始化後開始加鎖操作,而另外一個程序正在初始化互斥鎖,從而導致問題發生。為了避免這個問題就需要保證在鎖定互斥鎖的時候互斥鎖已經被初始化了,這就需要採用特定的程式執行模式,比如說主程序先初始化互斥鎖,然後再啟動其他程序;或者在互斥鎖初始化的過程中採用另外一種程序同步方式保證鎖一次且僅一次進行了初始化。從這個過程來看,採用程序間共享的互斥鎖並不比其他程序間協作方式方便,在實際使用中當然是用最方便的方式,所以程序間的互斥鎖在實際使用中並不是很普遍。參考連線 https://www.cnblogs.com/my_life/articles/4538299.html
    • 另外一個問題是持有鎖的程序發生了死鎖,從上面的連結瞭解到,“實測發現,down 了以後程序退出,互斥鎖仍然是被鎖定的。”,上文連結也提出了一種解決方法,這種解決方法是可行的。
    • 因此,最後的結論時,程序間互斥鎖是可行的,但是使用的時候要注意以上事項,沒有其他程序間通訊方法來的方便。

pthread_mutexattr_t

pthread_mutex_t

  1. 互斥鎖結構體.
  2. 靜態初始化:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  3. 動態初始化:
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);

pthread_mutex_init

  1. 互斥鎖初始化函式:
    int pthread_mutex_init( pthread_mutex_t * mutex, pthread_mutexattr_t * attr);

pthread_mutex_lock

  1. 函式原型: int pthread_mutex_lock(pthread_mutex_t * mutex);

pthread_mutex_unlock

  1. 函式原型:int pthread_mutex_unlock(pthread_mutex_t * mutex);

pthread_mutex_trylock

  1. 函式原型: int pthread_mutex_trylock(pthread_mutex_t * mutex);
  2. 當互斥量已經被鎖住時該函式將返回錯誤程式碼EBUSY。
  3. 使用非阻塞函式加鎖時,需要確保只有在獲得鎖成功後才能執行解鎖操作。只有擁有互斥量的執行緒才能解鎖它。
  4. 使用時需要判斷 != EBUSY,意味著加鎖成功,再判斷==0,意味著沒有別的錯誤發生,然後再執行功能程式碼。注意只有加鎖成功後才能釋放訊號量,沒有加鎖成功不能執行釋放訊號量的操作。

pthread_mutex_destory

  1. 函式原型: int pthread_mutex_destroy(pthread_mutex_t mutex);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

int count = 0;
pthread_mutex_t mutex;
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /靜態初始化互斥量

void * test( void * par ) {
        int i = 0;
        for(i = 0;i < 10; i++) {
                sleep(2);
                pthread_mutex_lock(&mutex); //互斥量加鎖
                count += 1;
                printf("%d\n", count);
                pthread_mutex_unlock(&mutex); //互斥量解鎖:
        }
}

void * test1(void * par) {
        int ret = 0, i = 0;
        for(i = 0; i < 10;i++) {
                ret = pthread_mutex_trylock(&mutex); //嘗試加鎖
                if(ret == EBUSY) {  //如果鎖被佔用,那麼立即返回EBUSY
                        printf("Mutex EBUSY.\n");
                } else if(ret == 0) { //表示加鎖成功
                        printf("Try lock successfully.\n");
                        count++;
                        pthread_mutex_unlock(&mutex); //只有在加鎖成功之後才能釋放互斥鎖
                }
                sleep(1);
        }
        return NULL;
}

int main(int argc, char ** argv) {
        pthread_t t1, t2, t3, t4;
        pthread_mutex_init(&mutex, NULL); //互斥量初始化
        pthread_create(&t1, NULL, test, NULL);
        pthread_create(&t2, NULL, test, NULL);
        pthread_create(&t4, NULL, test1, NULL);
        pthread_create(&t3, NULL, test, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        pthread_join(t4, NULL);
        pthread_mutex_destroy(&mutex); //互斥量銷燬
}

條件變數

  1. 程式碼示例1
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct {
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
} A;

A a = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};

void * producer(void * par) {
    sleep(3);
    for(int i = 0; i < 1; i++) {
        sleep(1);
        printf("Sending info.\n");
        pthread_mutex_lock(&(a._mutex));   //先加鎖再wait。
        //pthread_cond_signal(&(a._cond));  //signal的話,只有一個wait的執行緒會執行。
        pthread_cond_broadcast(&(a._cond)); //broadcast的話,兩個wait的執行緒都會執行。
        pthread_mutex_unlock(&(a._mutex));
    }
    printf("producer exit.");
    return NULL;
}
void * consumer(void * par) {
    pthread_mutex_lock(&(a._mutex));  //先加鎖再wait.
    pthread_cond_wait(&(a._cond), &a._mutex);
    pthread_mutex_unlock(&(a._mutex));
    printf("Consumer wake up. %ld\n", pthread_self());
    return NULL;
}

int main(int argc, char ** argv) {
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, consumer, NULL);
    pthread_create(&t2, NULL, producer, NULL);
    pthread_create(&t3, NULL, consumer, NULL);
    pthread_exit(0);
}

pthread_cond_t

  1. 條件變數是用來共享資料狀態資訊的。條件變數的作用是發訊號,而不是互斥。條件變數不提供互斥,需要一個互斥量來提供對共享資料的訪問,這就是為什麼在等待一個條件變數時必須指定一個互斥量。
  2. 動態初始化的條件變數需要呼叫pthread_cond_destroy來釋放。而靜態初始化的條件變數不需要呼叫pthread_cond_destroy來釋放。
  3. 條件變數提供兩種方式來喚醒等待條件變數的執行緒。一個是訊號,一個是廣播。傳送訊號只會喚醒一個等待執行緒。而廣播會喚醒所有等待該條件變數的執行緒(所有等待的執行緒都會執行)
  4. 總是在while迴圈等待一個條件變數是一個好的習慣。

pthread_cond_wait

  1. 等待條件變數
  2. 函式原型:
    int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);
    在阻塞執行pthread_cond_wait的執行緒之前,pthread_cond_wait將解鎖互斥量,這樣訊號及廣播操作才能夠加鎖互斥量,然後傳送訊號。在pthread_cond_wait獲得訊號之後,pthread_cond_wait會再次加鎖互斥量,然後線上程中執行互斥操作。
    pthread_cond_wait(&cond);函式在把執行緒放入等待佇列之後會把cond對應的互斥鎖進行解鎖,這樣其他的執行緒才能加鎖互斥鎖,然後進行相應的操作。在其他執行緒給條件變數傳送了訊號或者廣播之後會導致pthread_cond_wait函式退出,但是在退出之前pthread_cond_wait函式會重新把互斥鎖進行加鎖操作,這樣線上程後續的操作過程中互斥量是被鎖住的。

pthread_cond_signal

  1. 給條件變數傳送訊號
  2. 函式原型:
    int pthread_cond_signal( pthread_cond_t * cond );

pthread_cond_broadcast

  1. 廣播條件變數
  2. 函式原型:
    int pthread_cond_broadcast(pthread_cond_t * cond);

pthread_cond_timewait

  • 等待條件變數,定時超時。
  • 函式原型:
    int pthread_cond_timewait(
    pthread_cond_t * cond, //條件變數
    pthread_mutex_t * mutex, //互斥量
    struct timespec * expiration ); //超時時間結構體

pthread_cond_init

  • 靜態初始化
    pthread_cond_t condition = PTHREAD_COND_INITIALIZER; //不需要呼叫pthread_cond_destroy()釋放。
  • 動態初始化
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL); //需要呼叫pthread_cond_destroy()銷燬。
    函式原型: int pthread_cond_init( pthread_cond_t * cond, pthread_condattr_t * condattr );
    cond: 條件變數指標。
    condattr: 條件變數的屬性。

pthread_cond_destroy

  • 銷燬條件變數。
  • 函式原型:
    int pthread_cond_destroy( pthread_cond_t * cond );

訊號量

  • 訊號燈遵循傳統的unix形式的報錯機制,如果函式發生錯誤則返回-1,並且錯誤號儲存在errno中。
  • 標頭檔案 semaphore.h
#include <iostream>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

using namespace std;
//訊號量的值可以大於1,例如為10,sem_wait會把訊號量減1,sem_post會把訊號量加1.
//訊號量的所有函式都是失敗返回-1,對應錯誤程式碼儲存在errno裡。
sem_t sem;

void * fun1(void * par) {
        while(1) {
                sem_wait(&sem); //減1,如果到0則等待。
                cout << "Wait tid: " << pthread_self() << endl;
        }
        return NULL;
}

void * fun2(void * par) {
        while(1) {
                sleep(2);
                sem_post(&sem); //訊號量加1.
                sem_post(&sem); //訊號量加1.
                sem_post(&sem); //訊號量加1.
                cout << "Post tid: " << pthread_self() << endl;
        }
        return NULL;
}

int main(int argc, char ** argv) {
        sem_init(&sem, 0, 5); //初始化訊號量。
        pthread_t t1,t2,t3;
        pthread_create(&t1, NULL, fun1, NULL);
        pthread_create(&t2, NULL, fun1, NULL);
        pthread_create(&t3, NULL, fun2, NULL);
        pthread_exit(0);
        return 0;
}

sem_init

  • 訊號燈初始化函式,函式原型為:
  • int sem_init(sem_t * sem, int pshared, unsigned int value);
  • pshared為非0,表示訊號燈可以在程序間共享。pshared為0,表示訊號燈只能在同一個程序的執行緒之間共享。
  • (在程序間共享的訊號燈是否要建立在共享記憶體上? 是。)
  • sem表示訊號燈指標,value為要給訊號等賦的值。
  • man 說明
sem_init() initializes the unnamed semaphore at the address pointed to by sem. The value argument specifies the initial value for the semaphore.
The pshared argument indicates whether this semaphore is to be shared between the threads of a process, or between processes. (pshared引數表示訊號量是否可以在程序之間共享。)
If pshared has the value 0, then the semaphore is shared between the threads of a process, and should be located at some address that is visible to all threads (e.g., a global variable, or a variable allocated dynamically on the heap).
If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared memory (see shm_open(3), mmap(2), and shmget(2)). (Since a child created by fork(2) inherits its parent's memory mappings, it can also access the semaphore.) Any process that can access the shared memory region can operate on the semaphore using sem_post(3), sem_wait(3), etc. (如果pshared設定為非0,那麼訊號量可以在程序之間共享,並且如果想要在程序之間共享該訊號量的話應該把訊號量放置在共享記憶體區。)
Initializing a semaphore that has already been initialized results in undefined behavior.
(一個訊號量不能被初始化兩次。)

sem_destroy

  • 銷燬訊號燈。
  • int sem_destroy(sem_t * sem);
  • 如果銷燬成功返回0,在銷燬時如果還有其他執行緒在佔用訊號燈,則返回EBUSY,此時destroy函式會被阻塞。

sem_wait

  • 等待訊號燈。
  • int sem_wait(sem_t * sem);
  • 訊號燈值大於0的時候,sem_wait會把該值減1;如果訊號燈值等於0,則執行緒會被阻塞,直到訊號燈的值能夠成功被降低。
  • 當訊號燈初始化為1的時候,是一個鎖定操作;當初始化為0的時候,是一個等待操作。
  • 訊號燈可以初始化為大於1的值。

sem_trywait

  • 嘗試加鎖一個訊號燈,如果trywait的時候訊號燈大於0,則把訊號燈的值減去1;如果trywait的時候訊號燈已經為 0,則返回EAGAIN。

sem_post

  • int sem_post(sem_t * sem);
  • 把訊號量加1。

讀寫鎖

  • 讀寫鎖實際是一種特殊的自旋鎖。它把對共享資源的訪問者劃分為讀者和寫者。讀者只對共享資源進行讀訪問;寫者需要對共享資源進行寫操作和讀操作。一個讀寫鎖同時只能有一個寫鎖或者多個讀鎖,但是不能既有寫鎖又有讀鎖被加鎖。讀寫鎖適合於對資料結構的讀次數比寫次數多的情況。 讀寫鎖又叫共享-獨佔鎖。
  • 標頭檔案 pthread.h
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

pthread_rwlock_t rwlock;

void * reader(void * par) {
        while(1) {
                pthread_rwlock_rdlock(&rwlock);
                sleep(1);
                cout << "Pthread is locked: " << pthread_self() << endl;
                pthread_rwlock_unlock(&rwlock);
        }
        return NULL;
}

void * writer(void * par) {
        while(1) {
                pthread_rwlock_wrlock(&rwlock);
                cout << "Pthread is locked by writter." << endl;
                pthread_rwlock_unlock(&rwlock);
                sleep(2);
        }
        return NULL;
}

int main( int argc, char ** argv) {
        pthread_rwlock_init(&rwlock, NULL);
        pthread_t t1, t2, t3;
        pthread_create(&t1, NULL, reader, NULL);
        pthread_create(&t2, NULL, writer, NULL);
        pthread_create(&t3, NULL, reader, NULL);
        pthread_exit(0);
        return 0;
}

pthread_rwlock_init

  • int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr);
  • 成功返回0,失敗返回錯誤程式碼。
  • 不能多次對同一個讀寫鎖執行初始化操作,行為未定。

pthread_rwlock_rdlock

  • int pthread_rwlock_rdlock( pthread_rwlock_t * rwlock); 讀加鎖

pthread_rwlock_tryrdlock

  • int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock); 成功返回0,失敗返回EBUSY。

pthread_rwlock_wrlock

  • int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock); 寫加鎖

pthread_rwlock_trywrlock

  • int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock); 成功返回0, 失敗返回EBUSY。

pthread_rwlock_destroy

  • int pthread_rwlock_destroy(pthread_rwlock_t * rwlock); 銷燬讀寫鎖

pthread_rwlock_unlock

  • int pthread_rwlock_unlock(pthread_rwlock_t * rwlock); 解除鎖定。

讀寫鎖屬性設定相關函式

pthread_rwlockattr_t

  • 讀寫鎖屬性結構體

pthread_rwlockattr_init/pthread_rwlockattr_destroy

  • 初始化/銷燬讀寫鎖屬性物件。
  • 不能初始化一個已經被初始化了的讀寫鎖屬性物件。

pthread_rwlockattr_getpshared/pthread_rwlockattr_setpshared

  • 獲得讀寫鎖的程序間共享屬性。如果在程序間共享需要設定 PTHREAD_PROCESS_SHARED。

相關推薦

執行併發包學習總結Lock鎖

為什麼需要Lock鎖 1.因為我們需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。 2.通過Lock可以知道執行緒有沒有成功獲取到鎖 3.Lock鎖相當於汽車中的手動擋,相比synchron

linux/unix執行/程序程式設計總結

linux/unix多執行緒,多程序程式設計是在實際工作中經常使用到的技能,在C語言或者C++語言面試的時候也經常會被問到此部分內容。 本文對linux/unix系統中的pthread相關的多程序和多執行緒程式設計的各個方面進行了總結,包括執行緒、程序、程

Java執行之進階篇

一、執行緒池 1.1 執行緒池的建立 1.1.1 ThreadPoolExecutor 1.1.2 執行緒池的分類

Python執行的理解和使用Threading中join()函式的理解

1. 多執行緒的概念 多執行緒類似於同時執行多個不同程式,多執行緒執行有如下優點: 使用執行緒可以把佔據長時間的程式中的任務放到後臺去處理。 使用者介面可以更加吸引人,這樣比如使用者點選了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度  程式的執行速

D3D11和D3D12執行渲染框架的比較

1. 前言 D3D12伴隨DirectX12自2014年正式釋出以來已經近3年多時間了。遺憾的是我最近才有時間仔細研究D3D12介面及程式設計方面的內容。D3D12給我總體的感覺用一句話來概括就是——D3D12是一個“顯示卡作業系統!”。 得益於我對

linux 執行串列埠程式設計總結

最近在玩DJI M100,呼叫API獲取GPS位置時發現高程定位完全是錯的(負的幾百多米),查了一下文件說高程資料是由氣壓計得到的,而飛行控制時又需要比較可靠的高度資訊,於是乎決定用上我們實驗室的搭載Ublox晶片的板子搞事情,在子執行緒通過串列埠接收板子的定位結果,在主執

Qt執行程式設計總結

Qt對執行緒提供了支援,基本形式有獨立於平臺的執行緒類、執行緒安全方式的事件傳遞和一個全域性Qt庫互斥量允許你可以從不同的執行緒呼叫Qt方法。 這個文件是提供給那些對多執行緒程式設計有豐富的知識和經驗的聽眾的。推薦閱讀: 警告:所有的GUI類(比如,QWidget和它的

Qt執行程式設計總結所有GUI物件都是執行不安全的

Qt對執行緒提供了支援,基本形式有獨立於平臺的執行緒類、執行緒安全方式的事件傳遞和一個全域性Qt庫互斥量允許你可以從不同的執行緒呼叫Qt方法。 這個文件是提供給那些對多執行緒程式設計有豐富的知識和經驗的聽眾的。推薦閱讀: 警告:所有的GUI類(比如,QWidget和它的子類),作業系統核心類(比如,QPr

Java執行之進階篇

概述 1.基本原子類 1.1 AtomicBoolean 1.2 AtomicInteger和AtomicLong 2.引用原子類

JAVA執行機制第二彈:程式碼Thread的子類建立執行

在Java中,執行緒物件的建立是用Threa類或者它的子類建立。 在編寫Thread類的子類時,需要重寫父類的run()方法,其目的是規定執行緒的具體操作,否則執行緒就沒有開始的地方 在這裡,做一個小小總結:  ··線上程的編寫的時候,要重寫父類的run()方法,在ru

執行的那點事兒1--如何選擇執行

  多執行緒向來是一個讓程式設計師頭痛的一個問題,不只是初學者容易犯錯誤,很多老鳥也難免站著中槍。一旦出現問題很難定位和解決,除了可能因為程式設計者知識上的缺陷導致的疏漏外,另一個難題就是問題重現難度大,避免多執行緒導致BUG最好的方法就是預防。   首先,在開始進行多執行

java中執行的建立和啟動1

多執行緒概述 1.什麼是多執行緒 執行緒是程式執行的一條路徑,一個程序中可以包含多條執行緒;多執行緒併發執行可以提高程式的效率 2.程序和執行緒之間的關係 作業系統可以同時執行多個任務,每個任務就是程序;程序可以同時執行多個任務,每個任務就是執

基於QT的執行視訊監控的實現

二丶接著上一節,這節主要講,多屏分割,多屏相互切換   視訊監控很重要的一個環節就是多屏切換了,這裡主要實現的是 1,2,4,8,16,32,64 分屏的相互切換,最多是64分屏。(1)QT 常用到的佈

D3D11和D3D12執行渲染框架的比較

 1.命令列表及命令的原生並行性 至此如果你還沒有看暈的話,或者說你已經明白了前面的這些概念鋪墊之後,或許心中還有一個疑問就是為什麼說可以用多個命令列表來記錄可能不同的命令,最後再來執行,這樣

D3D11和D3D12執行渲染框架的比較

 1.CPU執行緒和GPU執行緒的區別 另外我們還需要深刻的理解的一個概念就是CPU執行緒和GPU執行緒的區別。 1.1.CPU執行緒 CPU執行緒在Windows作業系統中更多的是指一個

關於執行的幾點總結部落格園遷移

 關於執行緒 synchronized關鍵字:  不能用在變數和建構函式上  放在方法上面,鎖定的是物件,放在靜態方法上鎖定的是類  不應該鎖定常量,比如String等型別因為程式中這個物件難免還會用

D3D11和D3D12執行渲染框架的比較

1.     多執行緒的一些基礎知識和問題 1.1.    併發和並行 如果你對多執行緒程式設計理解比較深刻的話,那麼首先第一個要搞明白的概念就是“併發”和“並行”的區別,併發很多時候指的是在一個時間

libcurl執行超時設定不安全轉載

from http://blog.csdn.net/sctq8888/article/details/10031219 (1), 超時(timeout) libcurl 是 一個很不錯的庫,支援http,ftp等很多的協議。使用庫最大的心得就是,不仔細看文件,僅僅看著

java執行設計模式 -- 流水線模式Pipeline

十一、流水線模式(Pipeline) 1、核心思想 將一個任務處理分解為若干個處理階段,其中每個處理階段的輸出作為下一個處理階段的輸入,並且各個處理階段都有相應的工作者執行緒去執行相應的計算。 2、評價: 充分利用CPU,提高其計算效率。

使用執行進行網路圖片下載

一、執行緒阻塞的概念 在android中,主執行緒被阻塞會導致應用無法進行重新整理UI介面,應用無法響應,給使用者帶來不好的體驗。如果,主執行緒阻塞的時間過長,系統會丟擲ANR異常(Application Not Response). 任何耗時操作都不可以寫在主執行緒中,下