【Linux】線程安全(二)

一.POSIX信號量

  • 實現進程/線程間同步與互斥
  • 本質:計數器+等待隊列+等待+喚醒接口
  • 與條件變量對比:多了一個資源計數器,對臨界資源的計數,來進行條件判斷,是否可以對資源進行訪問。若不能訪問當前執行流,則阻塞當前執行流。
  • 依然接口爲庫函數。

1.使用

  • 在程序初始化階段,會根據資源的一個數量來初始化POSIX信號量(計數器),當執行流需要獲取資源的時候,調用信號量接口,來判斷信號量當中的計數器是否大於0,如果大於0,計數器-1,直接返回,獲取數據。如果不大於0,則阻塞該執行流。

2.接口

頭文件

#include<semaphore.h>

1.定義

sem_t sem;

2.初始化

sem_init(sem_t * sem,int pshared,int value)
sem:傳入信號量的地址
pshared:0代表線程 1代表進程
value:實際資源個數,用於信號量初始化

3.等待

sem_wait(sem_t* sem)----沒有資源就阻塞等待,等待信號量,將信號量計數器的值-1
sem_trywait(sem_t* sem)----沒有資源就報錯返回

4.喚醒(發佈信號量,資源使用完畢,可以歸還資源,信號量計數器+1)

  • 當執行流產生一個資源,是否需要喚醒
    計數器小於0,對計數器+1,則喚醒一個執行流。
    計數器小於0,對計數器+1,不喚醒執行流。
int sem_post(sem_t* sem)

5.銷燬

int sem_destroy(sem_t* sem)

3.信號量實現互斥功能

  • 保證初始化資源數量爲1即可實現互斥。

4.使用POSIX信號量實現生產者與消費者模型

  • 基於一個安全的環形隊列實現。可以採用數組,先進先出即可。
  • 當寫滿時,藉助計數器進行阻塞。(初始化告訴計數器數組大小)
#include <semaphore.h>
#include <pthread.h>
#include <cstdio>
#include <iostream>
#include <vector>

#define SIZE 1
#define THREADCOUNT 4

class RingQueue
{
    public:
        RingQueue()
            :Vec_(SIZE)
        {
            Capacity_ = SIZE;
            PosWrite_ = 0;
            PosRead_ = 0;

            //初始化同步的信號量
            //生產者生產的時候就看我們有多少空間可以供我們去生產
            sem_init(&ProSem_, 0, SIZE);
            //消費者初始化的時候是看當前有多少資源可以消費
            sem_init(&ConSem_, 0, 0);
            //初始化互斥
            sem_init(&LockSem_, 0, 1);
        }

        ~RingQueue()
        {
            sem_destroy(&ProSem_);
            sem_destroy(&ConSem_);
            sem_destroy(&LockSem_);
        }

        void Push(int& Data)
        {
            //對於加鎖而言,由於sem_wait並沒有通知LockSem進行釋放資源的操作,sem_wait(&ProSem_)內部並沒有 sem_post(&LockSem_)功能
            //如果現在需要往vec_裏面插入數據的時候
            //當時空間已經全部被插入了,沒有空間了
            //卡在了sem_wait(&ProSem_);阻塞等待 消費者來通知進行生產
            //但是這會兒由於生產者把鎖資源拿着,促使消費者想要消費數據的時候,拿不到
           //所以必須把LockSem放裏面!
            sem_wait(&ProSem_);

            sem_wait(&LockSem_);
            Vec_[PosWrite_] = Data;
            PosWrite_ = (PosWrite_ + 1) % Capacity_;
            sem_post(&LockSem_);

            //資源進行+1操作,並且喚醒消費者
            sem_post(&ConSem_);
        }

        void Pop(int* Data)
        {
            sem_wait(&ConSem_);

            sem_wait(&LockSem_);
            *Data = Vec_[PosRead_];
            PosRead_ = (PosRead_ + 1) % Capacity_;
            sem_post(&LockSem_);

            sem_post(&ProSem_);
        }
    private:
        std::vector<int> Vec_;
        size_t Capacity_;
        int PosWrite_;//讀位置
        int PosRead_;//寫位置
        //同步功能的信號量
        //生產者的信號量
        sem_t ProSem_;
        //消費者的信號量
        sem_t ConSem_;
        //實現互斥
        sem_t LockSem_;
};

void* ProStart(void* arg)
{
    RingQueue* rq = (RingQueue*)arg;
    int i = 0;
    while(1)
    {
        rq->Push(i);
        printf("ProStart make data [%p][%d]\n", pthread_self(), i);
        i++;
    }
    return NULL;
}

void* ConStart(void* arg)
{
    RingQueue* rq = (RingQueue*)arg;
    int Data;
    while(1)
    {
        rq->Pop(&Data);
        printf("ConStart consume data [%p][%d]\n", pthread_self(), Data);
    }
    return NULL;
}


int main()
{
    pthread_t Pro_tid[THREADCOUNT], Con_tid[THREADCOUNT];
    int i = 0;
    int ret = -1;
    RingQueue* rq = new RingQueue();
    for(; i < THREADCOUNT; i++)
    {
        ret = pthread_create(&Pro_tid[i], NULL, ProStart, (void*)rq);
        if(ret != 0)
        {
            printf("create thread failed\n");
            return 0;
        }
        ret = pthread_create(&Con_tid[i], NULL, ConStart, (void*)rq);
        if(ret != 0)
        {
            printf("create thread failed\n");
            return 0;
        }
    }

    for(i = 0; i < THREADCOUNT; i++)
    {
        pthread_join(Pro_tid[i], NULL);
        pthread_join(Con_tid[i], NULL);
    }
    delete rq;
    return 0;
}

Makefile

g++$^ -o $@ -g -lpthread

二.讀寫鎖

  • 使用場景:少量寫,大量讀。
  • 與互斥鎖類似,允許更高的並行
  • 讀寫鎖三種狀態
    1.讀模式下的加鎖
    2.寫模式下的加鎖狀態
    3.不加鎖的狀態

1.加鎖規則

  • 1.不能同時寫,可以同時讀
  • 2.一個執行流寫時,其他執行流不能讀也不能寫
  • 3.一個執行流讀時,其他執行流可以讀不能寫

細則

  • 1.一次只有一個線程佔有寫模式的讀寫鎖
  • 2.但是可以有多個線程同時佔有讀模式的讀寫鎖

問題1:已經有多個線程以讀模式獲取讀寫鎖,這時,有一個線程想以寫模式獲取讀寫鎖。
A:阻塞該進程,直到所有以讀模式打開的讀寫鎖全部釋放。

問題2:已經有多個線程以讀模式獲取讀寫鎖,這時,有一個線程想以寫模式獲取讀寫鎖,後面也有一些想讀的線程。會不會導致寫模式的線程一直阻塞?
A:通常不會長時間阻塞寫模式的線程。操作系統通常會阻塞後來想讀的線程,保證寫模式的線程不會等待太長時間。也避免鎖資源被長時間的佔用。

2.接口

1.定義

pthread_rwlock_t rwlock;

2.初始化

pthread_rwlock_init(pthread_rwlock_t*,pthread_rwlockattr_t*)

3.加鎖

pthread_rwlock_rdlock(pthread_rwlock_t* )
pthread_rwlock_wrlock(pthread_rwlock_t* )

4.解鎖

pthread_rwlock_unlock(pthread_rwlock_t* )

5.銷燬

pthread_rwlock_destroy(pthread_rwlock_t* )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章