【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* )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章