1. 程式人生 > >Linux 學習筆記—執行緒同步之讀寫鎖、自旋鎖、屏障

Linux 學習筆記—執行緒同步之讀寫鎖、自旋鎖、屏障

3.2.1 讀寫鎖 讀寫鎖和互斥體類似,不過讀寫鎖有更高的並行性,互斥體要麼是鎖住狀態,要麼是不加鎖狀態,而且一次只有一個執行緒可以對其加鎖。而讀寫鎖可以有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,否則返回錯誤碼

例項1 讀寫鎖

/**
 * 兩個讀執行緒讀取資料,一個寫執行緒更新資料
 */
#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;
    printf("data=%d\n",data);

    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("g_data=%d\n",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);//0
    pthread_create(&t2, NULL, func, (void *)READ_THREAD);//0
    pthread_create(&t3, NULL, func, (void *)WRITE_THREAD);//1

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

    pthread_rwlock_destroy(&g_rwlock);

    return 0;
}

3.2.2 自旋鎖(汽車等紅綠燈不熄火)   自旋鎖和互斥量類似,但它不是通過休眠使程序阻塞,而是在獲取鎖之前一直處於忙等(自旋)狀態,自旋鎖可用於下面的情況:鎖被持有的時間短,並且執行緒不希望再重新排程上花費太多的成本。自旋鎖通常作為底層原語用於實現其他型別的鎖。根據他們所基於的系統架構,可以通過使用測試並設定指令有效地實現。當然這裡說的有效也還是會導致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資源,其他執行緒需要獲取自旋鎖需要等待的時間更長了。

例項2 自旋鎖

#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;
}

3.2.3 屏障(瞭解)   屏障是使用者協調多個執行緒並行工作的同步機制,屏障允許每個執行緒等待,直到所有合作的執行緒都到達某一點,然後從該點出繼續執行。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的執行緒,則所有的執行緒會被喚醒。 一旦到達屏障計數值,而且執行緒處於非阻塞狀態,屏障就可以被重複使用。

例項3 屏障

#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;
}

補充:管道在多線之間通訊實現:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
Thread *m_Threads;
static int threadcount = 1;
void* work_thread(void* argc)
{
  Thread* param = (Thread*) argc;
  printf("childthread_tid=%lu\n", param->tid);
  int contant = 0;
  //sleep(2);
  printf("childthread--read return %d\n",read(param->notifyReceiveFd, &contant, sizeof(int)));
  printf("childthread--read from pipe %d\n", contant);
}

int main(int argc, char** argv)
{
     //在主執行緒和子執行緒之間建立管道
    m_Threads = malloc(sizeof(Thread) * threadcount);
    int fds[2];
    if( pipe(fds) )
    {
      perror("create pipe error");
    }
    m_Threads[0].notifyReceiveFd = fds[0];
    pthread_create(&m_Threads[0].tid, NULL,  work_thread, (void*)&m_Threads[0]);
    printf("mainthread_tid=%lu\n", m_Threads[0].tid);
    int contant = 1;
    // sleep(2);
    printf("mainthread--write %d to pipe\n", contant);
    printf("mainthread--write return %d\n",write(fds[1], &contant, sizeof(int)));
    pthread_join(m_Threads[0].tid, NULL);
    close(fds[0]);
    close(fds[1]);
    return 0;
}

在這裡插入圖片描述