你真的懂線程同步么?


  前言:學進程時,學習的重點應該進程間通信,而學習線程時,重點就應該是線程同步了。想過為什么?fork創建子進程之后,子進程有自己的獨立地址空間和PCB,想和父進程或其它進程通信,就需要各種通信方式,例如無名管道(管道,我習慣這么叫無名管道)、有名管道(命名管道)、信號、消息隊列、信號量、共享內存等;而pthread_create創建子線程之后,子線程沒有獨立的地址空間,大部分數據都是共享的,如果同時訪問數據,就是造成混亂,所以要控制,就是線程同步了。

  一、同步概念

  為什么要特意說一下同步概念呢?因為它跟其他領域的“同步”有些差異。

  所謂同步,即同時起步,協調一致。不同的對象,對“同步”的理解方式略有不同。如,設備同步,是指在兩個設備之間規定一個共同的時間參考;數據庫同步,是指讓兩個或多個數據庫內容保持一致,或者按需要部分保持一致;文件同步,是指讓兩個或多個文件夾里的文件保持一致。等等

       而,編程中、通信中所說的同步與生活中大家印象中的同步概念略有差異。“同”字應是指協同、協助、互相配合。主旨在協同步調,按預定的先后次序運行

  二、線程同步方式

  這篇博客主要介紹四種方式,如下:

方式 通用標識
互斥鎖(互斥量) pthread_mutex_
讀寫鎖  pthread_rwlock_
條件變量 pthread_cond_
信號量 sem_

    表中的“通用標識”,指的是那種同步方式的函數、類型都那么開頭的,方便記憶;還有其他方式,自旋鎖、屏蔽,感覺不常用,有興趣可以閱讀APUE。

   三、互斥鎖(互斥量)

  1、介紹

  先來畫個圖,來簡單說明一下:  PS:依舊是全博客園最丑圖,不接受反駁!

  

        

  Linux中提供一把互斥鎖mutex(也稱之為互斥量)。

  每個線程在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結束解鎖。

       資源還是共享的,線程間也還是競爭的,                                                                

       但通過“鎖”就將資源的訪問變成互斥操作,而后與時間有關的錯誤也不會再產生了。

  2、主要函數  

  pthread_mutex_init函數    //初始化mutex,默認為1

       pthread_mutex_destroy函數  //銷毀鎖

       pthread_mutex_lock函數     //加鎖,加鎖不成功,一直阻塞在那等待

       pthread_mutex_trylock函數   //嘗試加鎖,加鎖不成功,直接返回

       pthread_mutex_unlock函數  //解鎖

  以上5個函數的返回值都是:成功返回0, 失敗返回錯誤號。  

  pthread_mutex_t 類型,其本質是一個結構體。為簡化理解,應用時可忽略其實現細節,簡單當成整數看待。

  變量mutex只有兩種取值1、0。

  • pthread_mutex_init函數

  初始化一個互斥鎖(互斥量) ---> 初值可看作1

        原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

        參1:傳出參數,調用時應傳 &mutex      

        這個restrict關鍵字可能第一次遇到,說明一下:只用於限制指針,告訴編譯器,所有修改該指針指向內存中內容的操作,只能通過本指針完成。不能通過除本指針以外的其他變量或指針修改

         參2:互斥量屬性。是一個傳入參數,通常傳NULL,選用默認屬性(線程間共享)。互斥鎖也可以用於進程間同步,需要修改屬性為進程間共享。 參APUE.12.4同步屬性

  1. 靜態初始化:如果互斥鎖 mutex 是靜態分配的(定義在全局,或加了static關鍵字修飾),可以直接使用宏進行初始化。e.g.  pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
  2. 動態初始化:局部變量應采用動態初始化。e.g.  pthread_mutex_init(&mutex, NULL)

  其他函數就不解釋了,相對比較簡單。

  示例程序,主要對標准輸出進行加鎖,使主線程打印大寫“HELLO WORLD”,子線程打印小寫“hello world”,程序如下:

  

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

pthread_mutex_t mutex;

void err_thread(int ret, char *str)
{
    if (ret != 0) {
        fprintf(stderr, "%s:%s\n", str, strerror(ret));
        pthread_exit(NULL);
    }
}

void *tfn(void *arg)
{
    srand(time(NULL));

    while (1) {

        pthread_mutex_lock(&mutex);
        printf("hello ");
        sleep(rand() % 3);    /*模擬長時間操作共享資源,導致cpu易主,產生與時間有關的錯誤*/
        printf("world\n");
        pthread_mutex_unlock(&mutex);

        sleep(rand() % 3);

    }

    return NULL;
}

int main(void)
{
    int flag = 5;
    pthread_t tid;
    srand(time(NULL));

    pthread_mutex_init(&mutex, NULL);
    pthread_create(&tid, NULL, tfn, NULL);
    while (flag--) {

        pthread_mutex_lock(&mutex);

        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        pthread_mutex_unlock(&mutex);

        sleep(rand() % 3);

    }
    pthread_cancel(tid);                //  將子線程殺死,子線程中自帶取消點
    pthread_join(tid, NULL);

    pthread_mutex_destroy(&mutex);

    return 0;                           //main中的return可以將整個進程退出
}
View Code

  編譯時也要記得鏈上-pthread。

  四、讀寫鎖

  1、特性

  (1)讀寫鎖是“寫模式加鎖”時, 解鎖前,所有對該鎖加鎖的線程都會被阻塞。

  (2)讀寫鎖是“讀模式加鎖”時, 如果線程以讀模式對其加鎖會成功;如果線程以寫模式加鎖會阻塞。

  (3)讀寫鎖是“讀模式加鎖”時, 既有試圖以寫模式加鎖的線程,也有試圖以讀模式加鎖的線程。那么讀寫鎖會阻塞隨后的讀模式鎖請求。優先滿足寫模式鎖。讀鎖、寫鎖並行阻塞,寫鎖優先級高

         讀寫鎖也叫共享-獨占鎖。當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當它以寫模式鎖住時,它是以獨占模式鎖住的。寫獨占、讀共享。

         讀寫鎖非常適合於對數據結構讀的次數遠大於寫的情況。

  敲重點了,記住12個字:寫獨占、讀共享;寫鎖優先級高。

 

  2、主要函數  

   pthread_rwlock_init函數     //初始化

         pthread_rwlock_destroy函數  //銷毀鎖

         pthread_rwlock_rdlock函數    //讀加鎖,阻塞

         pthread_rwlock_wrlock函數   //寫解鎖,阻塞

         pthread_rwlock_tryrdlock函數  //嘗試讀解鎖

         pthread_rwlock_trywrlock函數 //嘗試寫加鎖

         pthread_rwlock_unlock函數  //解鎖

  以上7 個函數的返回值都是:成功返回0, 失敗直接返回錯誤號。 

        pthread_rwlock_t類型   用於定義一個讀寫鎖變量。

        pthread_rwlock_t rwlock;

  這些參考互斥鎖的函數,進行對比學習,只是多了讀鎖和寫鎖,就不過多解釋了。

  實例程序,3個線程“寫”全局變量,5個全局變量“讀”全局變量,程序如下:

  

/* 3個線程不定時 "寫" 全局資源,5個線程不定時 "讀" 同一全局資源 */

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

int counter;                          //全局資源
pthread_rwlock_t rwlock;

void *th_write(void *arg)
{
    int t;
    int i = (int)arg;

    while (1) {
        t = counter;
        usleep(1000);

        pthread_rwlock_wrlock(&rwlock);
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);

        usleep(5000);
    }
    return NULL;
}

void *th_read(void *arg)
{
    int i = (int)arg;

    while (1) {
        pthread_rwlock_rdlock(&rwlock);
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);

        usleep(900);
    }
    return NULL;
}

int main(void)
{
    int i;
    pthread_t tid[8];

    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write, (void *)i);

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);

    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL);

    pthread_rwlock_destroy(&rwlock);            //釋放讀寫瑣

    return 0;
}
View Code

 

  另兩種方式,還有條件變量和信號量,條件變量比較難理解,篇幅比較多,所以會另寫一篇博客來寫,敬請期待哦!   

         


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM