操作系统导论第30章——条件变量总结

1. 提出背景

如何实现父线程等待子线程的join功能?

基于自旋的方式可以实现,代码如下:

#include <pthread.h>
#include <cstdio>

volatile int done = 0;

void* child(void* arg) {
  printf("child\n");
  done = 1;
  return NULL;
}

int main() {
  printf("parent:begin\n");
  pthread_t c;
  pthread_create(&c, NULL, child, NULL);
  while (done == 0)
    ;
  printf("parent: end\n");
  return 0;
}

分析:可以实现,不过通过自旋的方式,浪费CPU时间,非常低效

解决办法:条件变量 

2. 条件变量定义

线程可以使用条件变量(condition variable)来等待一个条件变成真。条件变量是一个显式队列,当某些执行条件(即条件,condition)不满足时,线程可以把自己加入队列,等待该条件。另外某个线程,当它改变了上述状态时,就可以唤醒一个或者多个等待线程(通过在该条件上发信号),让它们继续执行。

核心观点:当前线程条件不满足则休眠等待,不占用CPU时间;满足条件则发送信号,唤醒等待线程。

3. Pthread基本语法

3.1 初始化信号量

pthread_cond_t c = PTHREAD_COND_INITIALIZER;

3.2 休眠等待信号

pthread_cond_wait(pthread_cond_t* c, pthread_mutex_t* m);

参数都是指针,除了信号量,还有锁(Pthread中称为互斥量) 

 3.3 发送信号唤醒等待线程

pthread_cond_signal(pthread_cond_t* c);

参数为信号量指针

 

由此解决背景中的问题,基于信号量的解决方案

#include <cstdio>
#include <pthread.h>


int done = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER;

void thr_exit() {
  pthread_mutex_lock(&m);
  done = 1;
  pthread_cond_signal(&c);
  pthread_mutex_unlock(&m);
}

void* child(void* arg) {
  printf("child\n");
  thr_exit();
  return NULL;
}

void thr_join() {
  pthread_mutex_lock(&m);
  while (done == 0)
    pthread_cond_wait(&c, &m);
  pthread_mutex_unlock(&m);
}

int main() {
  printf("parent: begin\n");
  pthread_t p;
  pthread_create(&p, NULL, child, NULL);
  thr_join();
  printf("parent: end\n");
  return 0;
}

分析:

1. 为什么要使用done? 

答:done实现了不同线程之间的交流,睡眠,唤醒和锁都离不开它。举个没有done的反例,子线程先运行thr_exit中的pthread_cond_signal发送信号,父线程后运行,若没有done,直接调用pthread_cond_wait,则将永远醒不来。

2. 为什么要有锁?

答:为了避免竞态条件,举个反例,若父线程先运行thr_join,执行到done == 0成立,该要运行pthread_cond_wait被系统中断运行子线程,由于没有锁子线程可以修改done,修改为1,然后切换到父线程,由于不会再次判断条件,则父线程运行pthread_cond_wait,则父线程将永远不会被唤醒。

经验总结:发信号时总是持有锁

3. 生产者/消费者问题(有界缓冲器)

3.1 问题描述

假设有一个或多个生产者线程和一个或多个消费者线程。生产者把生成的数据项放入缓冲区;消费者从缓冲区取走数据项,以某种方式消费。

3.2 代码

#include <pthread.h>
#include <cstdio>

#define MAX 10
int buffer[MAX];
int fill_ptr = 0;
int use_ptr = 0;
int count = 0;

pthread_cond_t empty, fill;
pthread_mutex_t mutex;

void put(int value) {
  buffer[fill_ptr] = value;
  fill_ptr = (fill_ptr + 1) % MAX;
  count++;
}

int get() {
  int tmp = buffer[use_ptr];
  use_ptr = (use_ptr + 1) % MAX;
  count--;
  return tmp;
}

void* producer(void* arg) {
  size_t loops = (size_t) arg; //注意参数类型转换
  for (int i = 0; i < loops; i++) {
    pthread_mutex_lock(&mutex);
    while (count == MAX)
      pthread_cond_wait(&empty, &mutex);
    put(i);
    pthread_cond_signal(&fill);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

void* consumer(void* arg) {
  size_t loops = (size_t) arg;
  for (int i = 0; i < loops; i++) {
    pthread_mutex_lock(&mutex);
    while (count == 0)
      pthread_cond_wait(&fill, &mutex);
    int tmp = get();
    pthread_cond_signal(&empty);
    pthread_mutex_unlock(&mutex);
    printf("%d\n", tmp);
  }
  return NULL;
}

 分析

1. 为什么判断条件用while不用if ?

答:考虑有两个消费者线程,一个生成者线程的情况,如果一个消费者线程1由于资源不足等待信号,接着生产者线程生成资源发生信号,消费者线程进入就绪状态,但是这个时候中断发生,消费者线程2抢先执行并且消费了资源,然后再切换到消费者线程1,由于使用if只判断一次,不用再判断是否有资源,消费者执行消费操作,但是资源已经没有了,出现错误。

2. 为什么需要两个条件变量

答:如果只有一个条件变量,消费者线程除唤醒生产者线程外还可以唤醒其他消费者线程,同样的生产者线程除了唤醒消费者线程也可以唤醒其他生产者线程。举个反例,如果有一个生产者线程,两个消费者线程,首先生成者线程生成了一个资源,然后唤醒消费者线程1,消费了资源,接着消费者发送信号,此时有可能唤醒生成者线程(正确)也可能唤醒消费者线程2(错误),如果唤醒了消费者线程2,它发现没有资源了于是也进入休眠,那么此时三个线程都进入了休眠,将全部无法醒过来!!

3. 为什么需要设置loops?

答:这样可以使得生成者线程可以一次性生成多个资源,消费者线程可以一次性消费多个资源,提高了并发度

经验总结:条件变量的时候while循环判断

4. 覆盖条件

这里主要谈到如果无法判断要唤醒哪一个线程,那么可以唤醒所有的线程,使用pthread_cond_broadcast()可以实现,当然这种方式会影响性能,应该尽量避免,但是有时候也是一种选择(比如简单多线程内存分配库再内存资源不足后调用处理程序,现在有了新的内存空间,那么该唤醒哪一个之前无法获取内存的线程呢,作者是直接使用唤醒所有线程)。

5. 问题扩展

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《分布式计算系统导论:原理与组成》是一本介绍分布式计算系统的入门学习资料。本书从系统介绍、原理讲解、组成要素、应用案例等多个方面进行了全面阐述。 首先,本书介绍了分布式计算系统的定义和基本概念。分布式计算系统是一种将任务分散到多个计算节点上进行并行处理的系统,它具有高性能、可靠性、可扩展性等特点。 接着,本书详细解释了分布式计算系统的原理和工作机制。说明了数据分布、并行计算、通信协议等关键技术,并介绍了分布式文件系统、分布式数据库、分布式存储等常用的组成要素。 然后,本书还通过实际应用案例来展示分布式计算系统的实际应用场景和解决问题的能力。例如,分布式计算系统在科学计算、云计算、大数据处理等领域具有广泛的应用。 最后,本书提供了相关教学参考和学习资源,包括习题和参考文献。这些资源对于读者深入理解分布式计算系统的原理和组成具有重要作用。 总之,《分布式计算系统导论:原理与组成》通过简明扼要的方式,全面介绍了分布式计算系统的原理和组成要素,深入浅出地向读者展示了分布式计算系统的基本概念、工作原理和应用案例。这本书对于学习和研究分布式计算系统的人员来说是一本很好的入门资料。 ### 回答2: 《分布式计算系统导论——原理与组成》是一本介绍分布式计算系统的相关原理和组成的书籍。分布式计算系统是指将任务或数据拆分为多个子任务或子数据,并在多个计算节点上进行并行计算和处理的一种系统。本书从理论和实践两方面详细介绍了分布式计算系统的基本原理和构成要素。 在原理方面,本书首先介绍了分布式计算系统的基本概念和发展历程,然后讲解了分布式计算系统的基本原理,包括任务划分与调度、通信与同步、容错与恢复等。这些原理是构建分布式计算系统的基石,对于理解和设计分布式计算系统非常重要。 在组成方面,本书详细讲解了分布式计算系统的各个组成部分。首先介绍了分布式文件系统,它是分布式计算系统的底层支撑,用于存储和管理分布式计算系统的数据。然后介绍了任务管理器和资源调度器,它们负责分配任务和资源给计算节点,确保系统能够高效地完成任务。接着介绍了通信中间件和数据传输协议,它们负责计算节点之间的通信和数据传输。最后介绍了容错和恢复机制,保证系统在节点故障时仍然能够正常运行。 总之,《分布式计算系统导论——原理与组成》是一本深入浅出地介绍分布式计算系统的理论和实践的书籍。通过学习本书,读者可以全面了解分布式计算系统的基本原理,以及构成分布式计算系统的各个组成部分。这将有助于读者在实际应用中设计和搭建高效可靠的分布式计算系统。 ### 回答3: 《分布式计算系统导论——原理与组成》是一本介绍分布式计算系统的教材。该教材主要围绕分布式计算系统的原理和组成展开讲解。 在原理方面,教材首先介绍了分布式计算系统的概念和基本原理。分布式计算系统是由多个计算节点组成的系统,节点之间通过网络进行通信和协作,共同完成任务。教材深入阐述了分布式系统的通信模型、一致性协议、容错原理等重要内容,使读者能够全面了解分布式计算系统的基本原理。 在组成方面,教材详细介绍了分布式计算系统的关键组成部分。其中包括分布式文件系统、分布式数据库、分布式调度器、分布式存储等。针对每个组成部分,教材都给出了其设计思路、实现原理和应用场景等详细信息,帮助读者全面掌握分布式计算系统的搭建和应用。 此外,教材还涉及了分布式计算系统的挑战和趋势。随着信息技术的不断发展,分布式计算系统面临着越来越多的挑战,如数据安全、任务调度等。教材对这些挑战进行了深入的分析,并提供了一些解决方案。同时,教材还展望了分布式计算系统的未来发展趋势,如云计算、边缘计算等,使读者对分布式计算系统的未来有一个清晰的认识。 总的来说,《分布式计算系统导论——原理与组成》通过深入浅出的讲解,帮助读者全面了解分布式计算系统的原理和组成。无论是学习分布式计算系统的初学者还是从事相关工作的专业人士,都能从该教材中获得宝贵的知识和经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值