1. 程式人生 > >Linux下執行緒同步的幾種常見方法

Linux下執行緒同步的幾種常見方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。

一、互斥鎖(mutex)
  鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。

 1. 初始化鎖
  int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
   其中引數 mutexattr 用於指定鎖的屬性(見下),如果為NULL則使用預設屬性。
   互斥鎖的屬性在建立鎖的時候指定,在LinuxThreads實現中僅有一個鎖型別屬性,不同的鎖型別在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。當前有四個值可供選擇:
   (1)PTHREAD_MUTEX_TIMED_NP,這是預設值,也就是普通鎖。當一個執行緒加鎖以後,其餘請求鎖的執行緒將形成一個等待佇列,並在解鎖後按優先順序獲得鎖。這種鎖策略保證了資源分配的公平性。
   (2)PTHREAD_MUTEX_RECURSIVE_NP,巢狀鎖,允許同一個執行緒對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同執行緒請求,則在加鎖執行緒解鎖時重新競爭。
   (3)PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個執行緒請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP型別動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。
   (4)PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖型別,僅等待解鎖後重新競爭。

 2. 阻塞加鎖
  int pthread_mutex_lock(pthread_mutex *mutex);
 3. 非阻塞加鎖
   int pthread_mutex_trylock( pthread_mutex_t *mutex);
   該函式語義與 pthread_mutex_lock() 類似,不同的是在鎖已經被佔據時返回 EBUSY 而不是掛起等待。
 4. 解鎖(要求鎖是lock狀態,並且由加鎖執行緒解鎖)
  int pthread_mutex_unlock(pthread_mutex *mutex);
 5. 銷燬鎖(此時鎖必需unlock狀態,否則返回EBUSY)
  int pthread_mutex_destroy(pthread_mutex *mutex);

  示例程式碼:

[[email protected]]$ cat mutextest.c


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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int gn;

void* thread(void *arg)
{
    printf("thread's ID is  %d\n",pthread_self());
    pthread_mutex_lock(&mutex);
    gn = 12;
    printf("Now gn = %d\n",gn);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main()
{
    pthread_t id;
    printf("main thread's ID is %d\n",pthread_self());
    gn = 3;
    printf("In main func, gn = %d\n",gn);
    if (!pthread_create(&id, NULL, thread, NULL))
    {
        printf("Create thread success!\n");
    }else
    {
        printf("Create thread failed!\n");
    }
    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;

}


[[email protected]]$ 


二、條件變數(cond)

  條件變數是利用執行緒間共享全域性變數進行同步的一種機制。條件變數上的基本操作有:觸發條件(當條件變為 true 時);等待條件,掛起執行緒直到其他執行緒觸發條件。
   
   1. 初始化條件變數
     int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
      儘管POSIX標準中為條件變數定義了屬性,但在Linux中沒有實現,因此cond_attr值通常為NULL,且被忽略。
   2. 有兩個等待函式 
      (1)無條件等待
         int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
      (2)計時等待
         int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
          如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,其中abstime以與time()系統呼叫相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。
 
      無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個執行緒同時請求(用 pthread_cond_wait() 或 pthread_cond_timedwait() 請求)競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在呼叫pthread_cond_wait()前必須由本執行緒加鎖(pthread_mutex_lock()),而在更新條件等待佇列以前,mutex保持鎖定狀態,並在執行緒掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。

   3. 激發條件
     (1)啟用一個等待該條件的執行緒(存在多個等待執行緒時按入隊順序啟用其中一個)  
         int pthread_cond_signal(pthread_cond_t *cond);
     (2)啟用所有等待執行緒
      int pthread_cond_broadcast(pthread_cond_t *cond); 

   4. 銷燬條件變數
     int pthread_cond_destroy(pthread_cond_t *cond);
      只有在沒有執行緒在該條件變數上等待的時候才能銷燬這個條件變數,否則返回EBUSY


說明:

  1. pthread_cond_wait 自動解鎖互斥量(如同執行了pthread_unlock_mutex),並等待條件變數觸發。這時執行緒掛起,不佔用CPU時間,直到條件變數被觸發(變數為ture)。在呼叫 pthread_cond_wait之前,應用程式必須加鎖互斥量。pthread_cond_wait函式返回前,自動重新對互斥量加鎖(如同執行了pthread_lock_mutex)。

  2. 互斥量的解鎖和在條件變數上掛起都是自動進行的。因此,在條件變數被觸發前,如果所有的執行緒都要對互斥量加鎖,這種機制可保證線上程加鎖互斥量和進入等待條件變數期間,條件變數不被觸發。條件變數要和互斥量相聯結,以避免出現條件競爭——個執行緒預備等待一個條件變數,當它在真正進入等待之前,另一個執行緒恰好觸發了該條件(條件滿足訊號有可能在測試條件和呼叫pthread_cond_wait函式(block)之間被髮出,從而造成無限制的等待)。

  3. 條件變數函式不是非同步訊號安全的,不應當在訊號處理程式中進行呼叫。特別要注意,如果在訊號處理程式中呼叫 pthread_cond_signal 或 pthread_cond_boardcast 函式,可能導致呼叫執行緒死鎖

示例程式碼1:

[[email protected]]$ cat condtest1.c


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

pthread_mutex_t mutex;
pthread_cond_t cond;

void hander(void *arg)
{
    free(arg);
    (void)pthread_mutex_unlock(&mutex);
}

void *thread1(void *arg)
{
    pthread_cleanup_push(hander, &mutex);
    while(1)
    {
        printf("thread1 is running\n");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        printf("thread1 applied the condition\n");
        pthread_mutex_unlock(&mutex);
        sleep(4);
    }
    pthread_cleanup_pop(0);
}

void *thread2(void *arg)
{
    while(1)
    {
        printf("thread2 is running\n");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        printf("thread2 applied the condition\n");
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

int main()
{
    pthread_t thid1,thid2;
    printf("condition variable study!\n");
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_create(&thid1,NULL,thread1,NULL);
    pthread_create(&thid2,NULL,thread2,NULL);

    sleep(1);

    do{
        pthread_cond_signal(&cond);
    }while(1);

    sleep(20);
    pthread_exit(0);

    return 0;
}

[[email protected]]$ 


示例程式碼2:

[[email protected]]$ cat condtest2.c


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

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

struct node
{
    int n_number;
    struct node *n_next;
}*head = NULL;

static void cleanup_handler(void *arg)
{
    printf("Cleanup handler of second thread.\n");
    free(arg);
    (void)pthread_mutex_unlock(&mtx);
}

static void *thread_func(void *arg)
{
    struct node *p = NULL;
    pthread_cleanup_push(cleanup_handler, p);

    while (1)
    {
        // 這個mutex主要是用來保證pthread_cond_wait的併發性。
        pthread_mutex_lock(&mtx);
        while (head == NULL)
        {
            /* 這個while要特別說明一下,單個pthread_cond_wait功能很完善,為何
            * 這裡要有一個while (head == NULL)呢?因為pthread_cond_wait裡的線
            * 程可能會被意外喚醒,如果這個時候head != NULL,則不是我們想要的情況。
            * 這個時候,應該讓執行緒繼續進入pthread_cond_wait
            * pthread_cond_wait會先解除之前的pthread_mutex_lock鎖定的mtx,
            * 然後阻塞在等待對列裡休眠,直到再次被喚醒(大多數情況下是等待的條件成立
            * 而被喚醒,喚醒後,該程序會先鎖定先pthread_mutex_lock(&mtx);,再讀取資源
            * 用這個流程是比較清楚的。*/

            pthread_cond_wait(&cond, &mtx);
            p = head;
            head = head->n_next;
            printf("Got %d from front of queue\n", p->n_number);
            free(p);
        }

        pthread_mutex_unlock(&mtx); // 臨界區資料操作完畢,釋放互斥鎖。
    }

    pthread_cleanup_pop(0);

    return 0;

}

int main(void)
{
    pthread_t tid;
    int i;
    struct node *p;

    /* 子執行緒會一直等待資源,類似生產者和消費者,但是這裡的消費者可以是多個消費者,
    * 而不僅僅支援普通的單個消費者,這個模型雖然簡單,但是很強大。*/

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

    sleep(1);

    for (i = 0; i < 10; i++)
    {
        p = (struct node*)malloc(sizeof(struct node));
        p->n_number = i;
        pthread_mutex_lock(&mtx); // 需要操作head這個臨界資源,先加鎖。

        p->n_next = head;
        head = p;

        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mtx); //解鎖

        sleep(1);
    }

    printf("thread 1 wanna end the line.So cancel thread 2.\n");

    /* 關於pthread_cancel,有一點額外的說明,它是從外部終止子執行緒,子執行緒會在最近的取消點,
    * 退出執行緒,而在我們的程式碼裡,最近的取消點肯定就是pthread_cond_wait()了。*/

    pthread_cancel(tid);

    pthread_join(tid, NULL);

    printf("All done -- exiting\n");

    return 0;
}

[[email protected]]$ 


可以看出,等待條件變數訊號的用法約定一般是這樣的:
...
pthread_mutex_lock(&mutex);
...
pthread_cond_wait (&cond, &mutex);
...
pthread_mutex_unlock (&mutex);
...

相信很多人都會有這個疑問:為什麼pthread_cond_wait需要的互斥鎖不在函式內部定義,而要使使用者定義的呢?現在沒有時間研究 pthread_cond_wait 的原始碼,帶著這個問題對條件變數的用法做如下猜測,希望明白真相看過原始碼的朋友不吝指正。

1. pthread_cond_wait 和 pthread_cond_timewait 函式為什麼需要互斥鎖?因為:條件變數是執行緒同步的一種方法,這兩個函式又是等待訊號的函式,函式內部一定有須要同步保護的資料。
2. 使用使用者定義的互斥鎖而不在函式內部定義的原因是:無法確定會有多少使用者使用條件變數,所以每個互斥鎖都須要動態定義,而且管理大量互斥鎖的開銷太大,使用使用者定義的即靈活又方便,符合UNIX哲學的程式設計風格(隨便推薦閱讀《UNIX程式設計哲學》這本好書!)。
3. 好了,說完了1和2,我們來自由猜測一下 pthread_cond_wait 函式的內部結構吧:
  int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
   {
      if(沒有條件訊號)
      {
         (1)pthread_mutex_unlock (mutex); // 因為使用者在函式外面已經加鎖了(這是使用約定),但是在沒有訊號的情況下為了讓其他執行緒也能等待cond,必須解鎖。
         (2) 阻塞當前執行緒,等待條件訊號(當然應該是類似於中斷觸發的方式等待,而不是軟體輪詢的方式等待)... 有訊號就繼續執行後面。
         (3) pthread_mutex_lock (mutex); // 因為使用者在函式外面要解鎖(這也是使用約定),所以要與1呼應加鎖,保證使用者感覺依然是自己加鎖、自己解鎖。
      }      
      ...
  }


三、 訊號量


 如同程序一樣,執行緒也可以通過訊號量來實現通訊,雖然是輕量級的。
   執行緒使用的基本訊號量函式有四個:

  #include <semaphore.h>

     1. 初始化訊號量
      int sem_init (sem_t *sem , int pshared, unsigned int value);

      引數:
      sem - 指定要初始化的訊號量;
      pshared - 訊號量 sem 的共享選項,linux只支援0,表示它是當前程序的區域性訊號量;
      value - 訊號量 sem 的初始值。

      2. 訊號量值加1
      給引數sem指定的訊號量值加1。
     int sem_post(sem_t *sem);

     3. 訊號量值減1
      給引數sem指定的訊號量值減1。
     int sem_wait(sem_t *sem);
      如果sem所指的訊號量的數值為0,函式將會等待直到有其它執行緒使它不再是0為止。

     4. 銷燬訊號量
    銷燬指定的訊號量。
  int sem_destroy(sem_t *sem);

  示例程式碼:

[[email protected]]$ cat semtest.c

#include <stdlib.h>

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>

#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!\n", __func__);return;}

typedef struct _PrivInfo
{
    sem_t s1;
    sem_t s2;
    time_t end_time;
}PrivInfo;

static void info_init (PrivInfo* prifo);
static void info_destroy (PrivInfo* prifo);
static void* pthread_func_1 (PrivInfo* prifo);
static void* pthread_func_2 (PrivInfo* prifo);

int main (int argc, char** argv)
{
    pthread_t pt_1 = 0;
    pthread_t pt_2 = 0;
    int ret = 0;
    PrivInfo* prifo = NULL;
    prifo = (PrivInfo* )malloc (sizeof (PrivInfo));

    if (prifo == NULL)
    {
        printf ("[%s]: Failed to malloc priv.\n");
        return -1;
    }

    info_init (prifo);
    ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, prifo);
    if (ret != 0)
    {
        perror ("pthread_1_create:");
    }

    ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, prifo);
    if (ret != 0)
    {
        perror ("pthread_2_create:");
    }

    pthread_join (pt_1, NULL);
    pthread_join (pt_2, NULL);
    info_destroy (prifo);
    return 0;
}

static void info_init (PrivInfo* prifo)
{
    return_if_fail (prifo != NULL);
    prifo->end_time = time(NULL) + 10;
    sem_init (&prifo->s1, 0, 1);
    sem_init (&prifo->s2, 0, 0);
    return;
}

static void info_destroy (PrivInfo* prifo)
{
    return_if_fail (prifo != NULL);
    sem_destroy (&prifo->s1);
    sem_destroy (&prifo->s2);
    free (prifo);
    prifo = NULL;
    return;
}

static void* pthread_func_1 (PrivInfo* prifo)
{
    return_if_fail (prifo != NULL);
    while (time(NULL) < prifo->end_time)
    {
        sem_wait (&prifo->s2);
        printf ("pthread1: pthread1 get the lock.\n");
        sem_post (&prifo->s1);
        printf ("pthread1: pthread1 unlock\n");
        sleep (1);
    }
    return;
}

static void* pthread_func_2 (PrivInfo* prifo)
{
    return_if_fail (prifo != NULL);
    while (time (NULL) < prifo->end_time)
    {
        sem_wait (&prifo->s1);
        printf ("pthread2: pthread2 get the unlock.\n");
        sem_post (&prifo->s2);
        printf ("pthread2: pthread2 unlock.\n");
        sleep (1);
    }
    return;

}

[[email protected]]$ 

也可參考:

相關推薦

Linux執行同步常見方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)  鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖  int pthread_mutex_init(pthread_mutex_t *mutex

Linux執行同步方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)   鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖   int pthread_mutex_init(pthread_mutex_t *m

執行實現方法?同步實現方法?

為何要使用同步? java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),  將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,  從而保證了該變數的唯一性和準確性。 同步的

4. 執行同步方式

執行緒同步的方式主要有以下四種:臨界區(Critical Section)、互斥量(Mutex)、訊號量(Semaphore)、事件(Event)的區別。 他們的主要區別和特點如下: 1)臨界區:通過對多執行緒的序列化來訪問公共資源或一段程式碼,速度快,適合控制資料訪問。在任意時刻只允許一個執行緒對共

概括的解釋執行可用狀態

1. 新建( new ):新建立了一個執行緒物件。 2. 可執行( runnable ):執行緒物件建立後,其他執行緒(比如 main 執行緒)呼叫了該物件 的 start ()方法。該狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲 取 cpu 的使用權 。 3

Jmeter元件-執行組-常見的定時器

1:執行緒組-幾種常見的定時器1.1: BeanShell Timer1.1.1:設定執行緒組如下圖 新增3個執行緒組 -->新增BeanShell Timer 設定延時3000ms1.1.2:觀察結果樹http請求1:第三秒請求資料 ;http請求1:第六秒請求資料

iOS 執行同步方式

多執行緒同步目的有以下幾個方面:第一,對一段程式碼的執行進行保護,如果同時執行一段程式碼,中間的臨時變數可能會互相干擾造成結果不對;第二,對資源的保護,多個執行緒執行不同的程式碼,但是可能涉及同一個資源;第三,訊息傳遞,一個執行緒通知另外一個執行緒發生了一件事。

java執行常用方法

join() Thread物件方法,用於使當前執行緒和後面的程式碼同步,使當前執行緒內操作執行完成後再執行後面的指令 wait() Thread物件方法,使當前執行緒進入等待狀態,等待狀態的執行緒不會去競爭資源 sleep() Thread類方法,使當前執行緒休眠指定時間

java多執行實現方法

          方法一:繼承Thread類,覆蓋方法run(), public class MyThread extends Thread { int count= 1, number; public MyThread(int num) { number = num;

Map執行安全實現方法

如果需要使 Map 執行緒安全,大致有這麼四種方法:  1、使用 synchronized 關鍵字,程式碼如下 synchronized(anObject) {    value = map.get(key); } 2、使用 JDK1.5提供的鎖(j

Java中建立執行方式以及執行同步方式

執行緒同步自己及基本就用過Thread和Runnable這兩種方式,還有其他很多方式如下: Executor框架簡介 建立執行緒有幾種不同的方式?你喜歡哪一種?為什麼? 而執行緒同步會用的方式就更少了,只會synchronized,其他方式如下: 關於執

linux執行的建立,同步和退出》

概述         前面有一篇文章專門講述了程序建立,監控和終止,這一篇文章進一步來談談執行緒的建立和同步等操作(這裡指的是POSIX規範下的執行緒,即Pthreads)。和探討程序的文章類似,還是通過講述相關呼叫的使用和注意事項來推進,並提供一些例項來做說明。 第一部

Linux執行間通訊及同步

1、Linux “執行緒” 程序與執行緒之間是有區別的,不過Linux核心只提供了輕量程序的支援,未實現執行緒模型。Linux是一種“多程序單執行緒”的作業系統。Linux本身只有程序的概念,而其所謂的“執行緒”本質上在核心裡仍然是程序。 大家知道,程序是資源分配的單位,同

Windows執行同步的實現方法

一種Windows下執行緒同步的實現方法 Windows下的多執行緒與執行緒同步概述 多工是一個作業系統可以同時執行多個程式的能力。基本上,作業系統使用一個硬體時鐘為同時執行的每個程序分配“時間片”。如果時間片足夠小,並且機器也沒有由於太多的程式而超負荷,那麼在使用者看來,所

Linux kernel多執行實現

驅動開發中常常會啟動幾個核心執行緒,在整個驅動生命週期期間執行某些操作,比如USB驅動的控制執行緒,一直等待SCSI命令,沒有命令的話睡眠,有命令的話就喚醒執行緒,解析執行相關的命令。還有USB驅動中的掃描執行緒,如果有新的裝置連線到USB匯流排,則會啟動掃描過程,平時時

【轉】關於在linux清屏的技巧

內容 可能 printf html get 技術分享 用不了 小寫 int 在windows的DOS操作界面裏面,清屏的命令是cls,那麽在linux 裏面的清屏命令是什麽呢?下面筆者分享幾種在linux下用過的清屏方法。 1、clear命令、這個命令將會刷新屏幕,本質上只

設計模式之單例模式【內附物件例項化方式、實現執行安全方式】

繼續來複習常用的設計模式-單例模式,順便回憶一下執行緒安全的幾種實現方式。 一、什麼是單例模式 單例模式,簡單常用的一種設計模式,也很好的體現了程式碼控制物件在記憶體數量的一種方式,主要分2種實現方式: ①餓漢式,執行緒安全 ②懶漢式,執行緒不安全(新增鎖機制,可以實現執行緒安全)

執行狀態以及sleep,wait,yield,join的區別

今天第一次寫部落格,因個人能力有限,不到位的地方請大家多多包涵 1.執行緒通常有五種狀態,建立,就緒,執行、阻塞和死亡狀態。 2.阻塞的情況又分為三種: (1)、等待阻塞:執行的執行緒執行wait()方法,該執行緒會釋放佔用的所有資源,JVM會把該執行緒

window執行同步之(Critical Sections(關鍵程式碼段、關鍵區域、臨界區域)----轉載

轉載:https://www.cnblogs.com/cyblogs/p/9948379.html    關鍵區域(CriticalSection) 臨界區是為了確保同一個程式碼片段在同一時間只能被一個執行緒訪問,與原子鎖不同的是臨界區是多條指令的鎖定,而原子

Linux執行同步機制

一、互斥鎖 儘管在Posix Thread中同樣可以使用IPC的訊號量機制來實現互斥鎖mutex功能,但顯然semphore的功能過於強大了,在Posix Thread中定義了另外一套專門用於執行緒同步的mutex函式。 1. 建立和銷燬    有兩種方法建