1. 程式人生 > >linux執行緒學習(3)

linux執行緒學習(3)

執行緒的同步

1. 互斥量

  1. 為什麼要使用互斥量:
    當多個執行緒共享相同的記憶體時,需要每一個執行緒看到相同的檢視。當一個執行緒修改變數時,而其他執行緒也可以讀取或者修改這個變數,就需要對這些執行緒同步,確保他們不會訪問到無效的變數
  2. 互斥鎖的初始化和銷燬:
    為了讓執行緒訪問資料不產生衝突,這要就需要對變數加鎖,使得同一時刻只有一個執行緒可以訪問變數。互斥量本質就是鎖,訪問共享資源前對互斥量加鎖,訪問完成後解鎖。當互斥量加鎖以後,其他所有需要訪問該互斥量的執行緒都將阻塞。當互斥量解鎖以後,所有因為這個互斥量阻塞的執行緒都將變為就緒態,第一個獲得cpu的執行緒會獲得互斥量,變為執行態,而其他執行緒會繼續變為阻塞,在這種方式下訪問互斥量每次只有一個執行緒能向前執行
  3. 加鎖和解鎖:
    加鎖
int pthread_mutex_lock(pthread_mutex_t *mutex); 
    //成功返回0,失敗返回錯誤碼。如果互斥量已經被鎖住,那麼會導致該執行緒阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
    //成功返回0,失敗返回錯誤碼。如果互斥量已經被鎖住,不會導致執行緒阻塞

解鎖

 int pthread_mutex_unlock(pthread_mutex_t *mutex);
    //成功返回0,失敗返回錯誤碼。如果一個互斥量沒有被鎖住,那麼解鎖就會出錯

例子:建立兩個執行緒,線上程中進行對全域性變數for迴圈自加,觀察有互斥鎖和沒有互斥鎖的全域性變數的結果

無互斥鎖:

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

//定義一全域性變數
int val;

void *thread_fun(void *arg)
{
        int i;
        for(i=0;i<10000;i++)
        {
            val++;
        }
    return(void *)0;
}
int main()
{
    pthread_t tid;

    //創造新執行緒,這裡建立兩個新執行緒
pthread_create(&tid, NULL, thread_fun, NULL); pthread_create(&tid, NULL, thread_fun, NULL); //等待新執行緒執行結束 pthread_join(tid, NULL); printf("val=%d\n",val);//列印經過兩次for迴圈後的val值,本應該是20000,但實際不是 return 0; }

這裡寫圖片描述
執行結果不為20000

有互斥鎖:

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

//定義兩個全域性變數
int val;
pthread_mutex_t mutex;//這個為互斥量

void *thread_fun(void *arg)
{
        int i;
        //加鎖,防止其他執行緒進行加鎖,防止產生錯亂
        pthread_mutex_lock(&mutex);
        for(i=0;i<10000;i++)
        {
            val++;
        }
        //一次迴圈完成,列印結果一次
        printf("一次迴圈完成,列印結果一次val=%d\n",val );
        pthread_mutex_unlock(&mutex);//解鎖

    return(void *)0;
}

int main()
{
    pthread_t tid;
    int err;
    //對互斥量進行初始化,只有初始化過到互斥量才能使用
    err = pthread_mutex_init(&mutex, NULL);
    if(err != 0)
    {
        printf("init mutex failed\n");
        return;
    }
    //創造新執行緒,這裡建立兩個新執行緒
    pthread_create(&tid, NULL, thread_fun, NULL);
    pthread_create(&tid, NULL, thread_fun, NULL);
    //等待新執行緒執行結束

    pthread_join(tid, NULL);
    printf("最後得到的值val=%d\n",val);//列印經過兩次for迴圈後的val值

    return 0;
}

執行結果:
這裡寫圖片描述

2. 讀寫鎖

  • 什麼是讀寫鎖,它與互斥鎖的區別:

    • 讀寫鎖與互斥量類似,不過讀寫鎖有更高的並行性。互斥量要麼加鎖要麼不加鎖,而且同一時刻只允許一個執行緒對其加鎖。對於一個變數的讀取,完全可以讓多個執行緒同時進行操作
    • pthread_rwlock_t rwlock
      讀寫鎖有三種狀態,讀模式下加鎖,寫模式下加鎖,不加鎖。一次只有一個執行緒可以佔有寫模式下的讀寫鎖,但是多個執行緒可以同時佔有讀模式的讀寫鎖
    • 讀寫鎖在寫加鎖狀態時,在它被解鎖之前,所有試圖對這個鎖加鎖的執行緒都會阻塞(無論是讀加鎖還是寫加鎖)。讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對其加鎖的執行緒都會獲得訪問權,但是如果執行緒希望以寫模式對其加鎖,它必須阻塞直到所有的執行緒釋放鎖
    • 當讀寫鎖讀模式加鎖時,如果有執行緒試圖以寫模式對其加鎖,那麼讀寫鎖會阻塞隨後的讀模式鎖請求。這樣可以避免讀鎖長期佔用,而寫鎖達不到請求。
    • 讀寫鎖非常適合對資料結構讀次數大於寫次數的程式,當它以讀模式鎖住時,是以共享的方式鎖住的;當它以寫模式鎖住時,是以獨佔的模式鎖住的。
      例子:建立兩個執行緒,分為以下三種情況:①執行緒一讀加鎖,執行緒二寫加鎖。②執行緒一讀加鎖,執行緒二讀加鎖。

情況①

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


pthread_rwlock_t rwlock;//定義一個讀寫鎖
int num=0;

void *thread_fun1(void *arg)
{
    int err;

    pthread_rwlock_rdlock(&rwlock);//讀模式下加鎖
    printf("thread 1 is begin\n");
    sleep(3);
    printf("thread 1 over\n");

    pthread_rwlock_unlock(&rwlock);

    return (void *)1;
}

void *thread_fun2(void *arg)
{
    int err;

    pthread_rwlock_wrlock(&rwlock);//寫模式下加鎖
    printf("thread 2 is begin\n");
    sleep(3);
    printf("thread 2 is over\n");
    pthread_rwlock_unlock(&rwlock);

    return (void *)1;
}

int main()
{
    pthread_t tid1, tid2;
    int err;

    err = pthread_rwlock_init(&rwlock, NULL);//初始化讀寫鎖
    if(err)
    {
        printf("init rwlock failed\n");
        return ;
    }

    err = pthread_create(&tid1, NULL, thread_fun1, NULL);//建立新執行緒1
    if(err)
    {
        printf("create new thread 1 failed\n");
        return ;
    }
    err = pthread_create(&tid2, NULL, thread_fun2, NULL);//建立新執行緒2
    if(err)
    {
        printf("create new thread 2 failed\n");
        return ;
    }

    pthread_join(tid1, NULL);//連線執行緒,等待執行緒執行結束
    pthread_join(tid2, NULL);

    pthread_rwlock_destroy(&rwlock);//銷燬讀寫鎖

    return 0;
}

執行結果:
這裡寫圖片描述
第一次執行:讀寫鎖在讀加鎖狀態時,另外執行緒寫模式對其加鎖,它必須阻塞到讀加鎖執行緒釋放鎖。第二次執行:讀寫鎖在寫加鎖狀態時,在它被解鎖之前,所有試圖對這個鎖加鎖(無論是讀加鎖還是寫加鎖)的執行緒都會阻塞。

情況②:將執行緒二的啟動函式程式碼改為:

void *thread_fun2(void *arg)
{
    int err;

    pthread_rwlock_rdlock(&rwlock);//讀模式下加鎖
    printf("thread 2 is begin\n");
    sleep(3);
    printf("thread 2 is over\n");
    pthread_rwlock_unlock(&rwlock);

    return (void *)1;
}

執行結果:
這裡寫圖片描述
讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對其加鎖的執行緒都會獲得訪問權