Linux入门篇——线程互斥

文章目录

  • 定义
    • 抢票:
  • 互斥锁:
    • 相关函数:
      • 销毁函数:
      • 初始化函数:
      • 宏初始化:(全局变量或者static)
      • 加锁:
      • 解锁:
    • 改进抢票:对临界区抢票代码进行加解锁处理
    • 死锁
      • 定义:
      • 四个必要条件:
        • 互斥条件:
        • 请求与保持条件:
        • 不剥夺条件:
        • 循环等待条件:
      • 避免死锁

定义

(1)临界资源:多执行流下被共享的资源 ,凡是被线程共享访问的资源都是临界资源(多线程、多进程打印数据到显示器)
(2)临界区:代码中访问临界资源的代码
(3)互斥、同步:实现线程访问控制的策略,实现对临界区或对临界资源的保护的功能
(4)互斥:在任意时刻,只允许一个执行流访问某段代码(某部分资源)
(5)同步:一般而言,让访问临界资源的过程在安全的前提下(一般是互斥、具有原子性的),让访问资源具有一定的顺序性–>让访问资源具有合理性

抢票:

// 抢票 1000票,5线程抢

int tickets = 1000;
void* ThreadRoutine(void *args){
    int id = *(int*)args;
    delete (int*)args;
    while(true){
        if(tickets>0){
            // 抢票
            usleep(1000);
            std::cout<<"【"<<id<<"】:"<<tickets<<std::endl;
            tickets--;

        }else{
            break;
        }
        //sleep(1);
    }
}

int main(){
    pthread_t tid[5];
    for(int i  = 0;i<5;i++){
        int *id = new int(i);
        pthread_create(tid+i,nullptr,ThreadRoutine,id);
    }
    for(int i = 0;i<5;i++){
        pthread_join(tid[i],nullptr);
    }

    return 0;
}

运行发现出现了tickets<0的情况!
Linux入门篇——线程互斥_第1张图片

原因:tickets是临界资源,不是原子的;
tickets–: ①数据由内存加载到CPU②CPU进行–计算③数据重新写到内存中,可能出现一个线程这三步没有全部完成就切换到了下一个线程,从而导致语句中断(线程切换:1.时间片到了2.信号检测:从内核态返回用户态时)
为了避免这种问题,需要对临界区进行加锁

互斥锁:

相关函数:

销毁函数:

int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:pthread_mutex_t *mutex:锁的地址

初始化函数:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数:pthread_mutex_t *restrict mutex:锁的地址
const pthread_mutexattr_t *restrict attr:锁的属性,设为空

宏初始化:(全局变量或者static)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

加锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

解锁:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

改进抢票:对临界区抢票代码进行加解锁处理

#include // C++内的
// 抢票 1000票,5线程抢
class Ticket{
    private:
    int tickets;
    pthread_mutex_t mtx;//系统级别,原生线程库
    std::metux mymtx;// c++语言级别

    public:
    Ticket():tickets(1000){
        pthread_mutex_init(&mtx,nullptr);
    }
    bool GetTicket(){
        pthread_mutex_lock(&mtx);
        // 保护临界区
        if(tickets>0){
            // 抢票
            usleep(1000);
            std::cout<<"【"<<pthread_self()<<"】:"<<tickets<<std::endl;
            tickets--;
            pthread_mutex_unlock(&mtx);

            return true;
        }else{
            printf("售罄\n");
            pthread_mutex_unlock(&mtx);

            return false;
        }
    }
    ~Ticket(){
        pthread_mutex_destroy(&mtx);
    }
};
 void* ThreadRoutine(void *args){
    Ticket* t= (Ticket*)args;
    while(true){
        if(t->GetTicket()){
            continue;
        }else{
            break;
        }
    }
}

int main(){
    Ticket *t = new Ticket();
    pthread_t tid[5];
    for(int i  = 0;i<5;i++){
        int *id = new int(i);
        pthread_create(tid+i,nullptr,ThreadRoutine,(void*)t);
    }
    for(int i = 0;i<5;i++){
        pthread_join(tid[i],nullptr);
    }

    return 0;
}

Linux入门篇——线程互斥_第2张图片
可以看到,设置了互斥锁不会再出现票数小于0的情况
注意:
在访问临界资源tickets之前,需要先访问mtx,因此需要所有线程必须先看到mtx,由此可得出,锁本身也是一种临界资源,因此,为了保证锁本身是安全的,需要lock、unlock是原子性的

线程安全和可重入:
线程安全描述的是线程之间互相影响的一种可能性
重入描述的是函数可不可以被重复进入

死锁

定义:

一组执行流因为某些场景而导致它占有了锁资源不释放,同时还在申请对方的资源而处于的永久等待状态

四个必要条件:

互斥条件:

一个资源每次只能被一个执行流使用

请求与保持条件:

一个执行流因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:

一个执行流已获得的资源,在末使用完之前,不能强行剥夺

循环等待条件:

若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

(1)破坏死锁的四个必要条件
(2)加锁顺序一致
(3)避免锁未释放的场景
(4)资源一次性分配

你可能感兴趣的:(os,c++,开发语言)