首页 > 其他分享 >锁 条件变量 信号量

锁 条件变量 信号量

时间:2024-01-28 16:12:05浏览次数:242  
标签:变量 lk 信号量 while mutex 条件 cond cv wait

锁-条件变量-信号量

用一个例子引入,用于输出嵌套深度不超过n的括号序列。

void Tproduce() {
  while (1) {
    mutex_lock(&lk);
    if (!CAN_PRODUCE) {
      cond_wait(&cv, &lk);
    }
    printf("("); count++;
    cond_signal(&cv);
    mutex_unlock(&lk);
  }
}

void Tconsume() {
  while (1) {
    mutex_lock(&lk);
    if (!CAN_CONSUME) {
      cond_wait(&cv, &lk);
    }
    printf(")"); count--;
    cond_signal(&cv);
    mutex_unlock(&lk);
  }
}

上面代码错误的原因在于被唤醒时,没有循环检查条件,导致产生了不合法的序列

修改了之后呢?

void Tproduce() {
  while (1) {
    mutex_lock(&lk);
    while (!CAN_PRODUCE) {
      cond_wait(&cv, &lk);
    }
    printf("("); count++;
    cond_signal(&cv);
    mutex_unlock(&lk);
  }
}

void Tconsume() {
  while (1) {
    mutex_lock(&lk);
    while (!CAN_CONSUME) {
      cond_wait(&cv, &lk);
    }
    printf(")"); count--;
    cond_signal(&cv);
    mutex_unlock(&lk);
  }
}

这样的代码在运行时会阻塞,什么也输出不了,发生了死锁。
原因在于使用cond_signal时,只会随机唤醒一个线程,生产者和消费者都有可能被唤醒。
一个极端的例子n=1,生产者消费者各有两个P1, P2, C1, C2
可能的执行序列为
P1执行,输出'(',此时P2,C1,C2还未执行,因此没有线程被唤醒,再次进入循环后wait
P2执行,wait
C1执行,输出')',唤醒P2,再次进入循环wait
C2执行,wait
P2执行,输出'(',唤醒P1,再次进入循环wait
P1执行,wait
至此,所有线程都在wait,死锁。
根本原因就是P1丢失了一次唤醒并且唤醒的进程是随机的,改正的方法就是使用cond_broadcast,唤醒所有线程。

void Tproduce() {
  while (1) {
    mutex_lock(&lk);
    while (!CAN_PRODUCE) {
      cond_wait(&cv, &lk);
    }
    printf("("); count++;
    cond_broadcast(&cv);
    mutex_unlock(&lk);
  }
}

void Tconsume() {
  while (1) {
    mutex_lock(&lk);
    while (!CAN_CONSUME) {
      cond_wait(&cv, &lk);
    }
    printf(")"); count--;
    cond_broadcast(&cv);
    mutex_unlock(&lk);
  }
}

到这里终于得到了勉强正确的代码,但broadcast显然是不太合理的。

但将信号量看做条件变量的一种特例,却不存在这样的问题。信号量会准确地执行V操作,不会丢掉唤醒机会。信号量的伪代码如下

void P(sem_t *sem){
    wait_until(sem.count > 0){
        sem.count--;
    }
}

void V(sem_t *sem){
    sem.count++;
}

P失败时立即睡眠,V后唤醒等待线程。
count = 1就变成了互斥锁

另一个例题如何打印随机的<><和><>,中间用_隔开
使用锁和条件变量时,将打印的过程视为在状态机上输入字符(这里是输出),进入后一个可行的状态。

mutex_t lk = MUTEX_INIT();
cond_t cv = COND_INIT();
enum {A=1,B,C,D,E,F};
int state = A;
struct mov{
    int from, ch, to;
};
struct mov moves[] = {
    {A,'<',B},
    {B,'>',C},
    {C,'<',D},
    {A,'>',E},
    {E,'<',F},
    {F,'>',D},
    {D,'_',A},
};
const char* roles = ".<<<<<>>>>___";
int sz;
void fish(int id){
    char role = roles[id];
    while(1){
        mutex_lock(&lk);
        for(;;){
            struct mov* p = moves;
            for(;p!=moves+sz;p++){
                if(p->from == state && p->ch == role){
                    break;
                }
            }
            if(p==moves+sz){
                cond_wait(&cv,&lk);
            }else{
                state = p->to;
                printf("%c",role);
                cond_broadcast(&cv);
                break;
            }
        }
        mutex_unlock(&lk);
    }
}
int main(){
    sz = sizeof(moves)/sizeof(struct mov);
    int n = strlen(roles);
    for(int i=0;i<n;i++){
        create(fish);
    }
}

但是,如果我们想用信号量实现呢?似乎不是很直观,因为信号量只能保证互斥和简单的同步,而不能保证状态机的正确顺序。
此时PV操作显然不能对状态进行,而要对待输出字符进行,对每个字符维护一个条件变量,P操作尝试下一个输出字符,到达可行状态后,在这个状态所有能前进的字符中选择一个,进行V操作。
这个不太好想,要用到计算图的思想。

void fish(int id){
    char role = roles[id];
    for(;;){
        P(&sems[role]);
        struct mov* p = moves;
        for(;p!=moves+sz;p++){
            if(p->from == state && p->ch == role){
                break;
            }
        }
        state = p->to;
        printf("%c",role);
        p = moves;
        int choices[4],n=0;
        for(;p!=moves+sz;p++){
            if(p->from == state){
                choices[n++] = p->ch;
            }
        }
        int c = rand()%n;
        V(&sems[choices[c]]);
    }
}

感觉记性真的越来越不好了,有些明明已经较深入学习过的内容还是忘了。

标签:变量,lk,信号量,while,mutex,条件,cond,cv,wait
From: https://www.cnblogs.com/wangerblog/p/17992958

相关文章

  • 无涯教程-Swift - 变量声明
    变量为我们提供了程序可以操纵的命名存储,Swift4中的每个变量都有一个特定的类型,该类型确定变量的内存大小和布局。Swift4支持以下基本类型的变量-Int或UInt  - 用于整数。更具体地说,可以使用Int32,Int64定义32或64位有符号整数,而UInt32或UInt64定义32或64位无符号整数......
  • python02-变量及输出
    目标变量的作用定义变量认识数据类型一.变量的作用举例体验:我们去图书馆读书,怎么样快速找到自己想要的书籍呢?是不是管理员提前将书放到固定位置,并把这个位置进行了编号,我们只需要在图书馆中按照这个编号查找指定的位置就能找到想要的书籍。这个编号其实就是把书籍存放的......
  • 面向对象基础 成员变量、成员方法、构造方法、this关键字、静态字段、静态方法..
    成员变量与局部变量的区别:  1.作用域:成员变量作用于整个类,局部变量只作用于它所属的范围(函数、语句)  2.生命周期&位置:成员变量存储在堆内存中,是属于对象的,随着对象存在消失。局部变量存储在栈内存中,是属于他所属的范围的,使用完自动释放。  3.初始值:成员变量有默认初始......
  • spring框架 - 通过 @Conditional注解来条件化地应用注解
    条件化地应用注解什么意思呢?参考chatGPT的回答:Spring框架允许您通过@Conditional注解来条件化地应用注解。@Conditional注解用于在特定条件下决定是否应用某个注解或配置。这在很多情况下是非常有用的,特别是在需要根据应用的运行时环境或配置来动态地选择性地应用某些注解......
  • C++类指针未初始化导致访问成员变量时报段错误
    #安装gcc和g++yuminstallgccyuminstallgcc-c++.x86_64//a.cpp#include<iostream>#include<unistd.h>usingnamespacestd;classTest{public:  voidtest1(){  }  voidtest2(){    age=10;  }private:  intage;}......
  • 2024 上海个人购买新能源电动汽车送牌条件 All In One
    2024上海个人购买新能源电动汽车送牌条件AllInOne上海购买电动车条件上海市鼓励购买和使用新能源汽车实施办法上海市人民政府办公厅关于转发市发展改革委等五部门制订的《上海市鼓励购买和使用新能源汽车实施办法》的通知发布日期:2021-02-10第四条(消费者)本实施办法所......
  • 环境变量管理工具Modules的安装
    一、软件依赖tcl软件(1)tcl下载地址:http://www.tcl-lang.org/software/tcltk/(2)编译安装cdunix/./configure--prefix=/usr/local/tcl(aptinstalltcltcl-dev)二、编译安装modules软件(1)下载地址:  https://modules.sourceforge.net/(2)解压安装:(apt-getinstallenvironme......
  • kettle从入门到精通 第三十五课 kettle 变量
    1、设置变量a、可以通过转换中的“设置变量”步骤进行设置。 b、手动通过kettle.properties文件或通过“编辑”菜单中的“设置环境变量”对话框来定义变量。kettle.properties文件的位置可以通过设置KETTLE_HOME来指定,如设置KETTLE_HOME为/opt/kettle/home,则kettle.propert......
  • python中利用变量解压列表、元组、字符串、字典、文件对象、迭代器和生成器等序列
    一、如果知道序列中元素的个数,可以直接进行变量赋值。coords=(102,40)lon,lat=coordsprint(lon)print(lat)text="news"a,b,c,d=textprint(a)print(b)print(c)print(d)二、如果不知道序列中元素的个数,可以通过*变量名来代表多个元素的变量,无论序列是什......
  • SpringBoot中Bean的条件装配
    目录概述ProfileConditionalConditionalOnConditionalOnProperty概述众所周知,SpringBoot最腻害的地方就是容器,开发人员的日常工作就是编写bean,并由框架扫描存到容器里面,当程序跑起来的时候,各种bean协同工作完成了软件功能。那么容器是什么呢?从概念层面来讲,容器是一个池子;从物......