xym-ee
  • 计算机与嵌入式开发学习
  • 1-1.编程基础
    • C 语言
      • C 中的数据
      • C 语言基础
      • 字符输入输出
      • 函数
      • 数组和指针
      • 字符串处理
      • 存储类别
      • 文件 I/O
      • 复杂数据类型
      • 位操作
      • 预处理和 C 库
    • 数据结构和算法入门
    • leetcode 刷算法题
      • 递归与栈
      • 二叉树与递归
      • 回溯问题
      • 动态规划 1
    • 基本工具和使用
      • shell
      • shell 脚本
      • vim 编辑器
      • 命令行数据整理
      • 命令行环境和配置
  • 1-2.计算机系统基础(CSAPP)
    • 1.计算机基础
    • 2.数据的表示
    • 3.加减运算
    • 4.乘除运算
    • 5.程序的表示转换和链接
    • 6.IA32指令
    • 7.过程调用
    • 10.程序的链接
  • 1-3.数字电路、计算机组成
    • 1.数字电路、virtual circuit board
    • 2.计算机组成/steam:Turing Complete
    • 3.微机原理与接口技术(8086)
  • 1-4.计算机网络
    • 1.从浏览器开始
    • 2.协议栈和网卡
    • 3.网络设备
    • 4.运营商、接入网
    • 5.服务器
    • 6.数据返回浏览器
    • socket编程
  • 1-5.操作系统
    • 0.绪论
      • 1.应用视角的操作系统
      • 2.硬件视角的操作系统
      • 3.数学视角的操作系统
      • 4.状态机模型的应用
    • 1.并发
      • 1.并发 bug 的解决思路
      • 2.互斥
      • 3.同步
      • 4.信号量
      • 5.真实并发
      • 6.调试技巧
      • 7.os kernel 实现
    • 2.虚拟化
      • 1.操作系统上的进程
      • 2.进程的地址空间
      • 3.系统调用和unix shell
      • 4.C 标准库的实现
      • 5.linux 操作系统
      • 6.可执行文件和加载
      • 7.动态链接和加载
      • 8.内核的实现
      • 9.fork 的应用
    • 3.持久化
      • 1.存储设备的原理
      • 2.输入输出设备模型
      • 3.设备驱动程序
      • 4.文件系统 API
      • 5.fat 和 unix 文件系统
      • 6.持久数据的可靠性
    • 总结
  • 2-1.嵌入式裸机开发
    • 嵌入式系统通信接口与协议
    • cortex-m 内核芯片裸机开发
    • MPU
  • 2-2.中等规模系统开发
    • LVGL 图形库
    • 裸机开发的软件框架
    • 基于 rtos 开发
  • 2-3.armv7-m架构与 rtos 原理
    • armv7-m 架构
    • rt-thread 内核实现
    • rt-thread 驱动开发
  • 3-1.linux 应用开发基础
  • 3-2.linux 镜像构建
    • uboot 使用
    • uboot 适配
    • uboot 启动分析
    • uboot 自定义命令
    • linux 内核适配
    • linux 内核启动分析
    • busybox 根文件系统构建
  • 3-3.linux 驱动开发
    • 驱动开发基础
    • sysfs
    • led 驱动
    • 设备树
    • pinctrl 和 gpio 子系统
    • 并发控制
由 GitBook 提供支持
在本页
  • 什么是信号量
  • 理解1
  • 理解2
  • 信号量实现生产者-消费者
  • 信号量的应用
  • 哲学家吃饭问题

这有帮助吗?

  1. 1-5.操作系统
  2. 1.并发

4.信号量

我们分析了同步的本质需求:两个并发的线程等待某个同步条件达成,完成时间线的 “交汇”。相应地,我们有了条件变量实现同步,并且解决了生产者-消费者问题 (括号打印问题)。

本讲内容:另一种共享内存系统中常用的同步方法:信号量 (E. W. Dijkstra)

  • 什么是信号量

  • 信号量适合解决什么问题

  • 哲学家吃饭问题

上次的内容,生产者-消费者问题,一种万能的同步机制:条件变量。我们先需要一把锁,在持有锁的情况下检查条件是否成立,以此实现同步。

什么是信号量

信号量是条件变量的特例。

//消费
void P(sem_t *sem) { // wait
  wait_until(sem->count > 0) {
    sem->count--;
  }
}

//生产
void V(sem_t *sem) { // post (signal)
  sem->count++;
}

有点生产者、消费者的味道。区别

  • P 失败时立即睡眠等待

  • 执行 V 时,唤醒任意等待的线程

理解1

初始时 count = 1 的特殊情况,信号量就变成了互斥锁。

#define YES 1
#define NO 0

void lock() {
  wait_until(count == YES) {
    count = NO;
  }
}

void unlock() {
  count = YES;
}

隐含假设:我们 unlock 的时候 count 一定是0。

互斥锁每次只能让一个人进入。

完全没有必要,因为可以让更多的人一起进来。

理解2

  • P - prolaag (try + decrease/down/wait/acquire)

    • 试着从袋子里取一个球

    • 如果拿到了,离开

    • 如果袋子空了,排队等待

  • V - verhoog (increase/up/post/signal/release)

  • 往袋子里放一个球

    • 如果有人在等球,他就可以拿走刚放进去的球了

    • 放球-拿球的过程实现了同步

PV 是荷兰语。

信号量即资源的数量。

信号量实现生产者-消费者

#include "thread.h"
#include "thread-sync.h"

sem_t fill, empty;

void Tproduce() {
  while (1) {
    P(&empty);
    printf("(");
    V(&fill);
  }
}

void Tconsume() {
  while (1) {
    P(&fill);
    printf(")");
    V(&empty);
  }
}

int main(int argc, char *argv[]) {
  assert(argc == 2);
  SEM_INIT(&fill, 0);
  SEM_INIT(&empty, atoi(argv[1]));
  for (int i = 0; i < 8; i++) {
    create(Tproduce);
    create(Tconsume);
  }
}

我们需要两个信号量,有两类线程,生产者每打一个 ( 就意味着有一个线程可以打印 ) ,即创造了一个资源。同样的,右括号同理。

因此一开始需要给这两个信号量初值。

empty->val = 3;
fill-val = 0;

有两个信号量。这个代码很漂亮。很干净。

整个这代码密度就要比前面的简单。在“一单位资源”很明确的情况下,确实很好用,也应该用。

信号量的应用

信号量适合干什么,不适合干什么。

典型应用 1 :先 A 后 B 的同步操作。(初始是锁定状态的互斥锁。)

  • 实现一次临时的 happens-before

    • 初始:s = 0

    • A; V(s)

    • P(s); B

典型应用 2 :带计数型的同步

  • 初始:done = 0

  • Tworker: V(done)

  • Tmain: P(done)

这两种方式都可以实现线程的 join()

哲学家吃饭问题

遇到的问题:死锁。并发编程一定会遇到这个问题,幸运的是,这个 bug 是比较容易解决的。

上一页3.同步下一页5.真实并发

最后更新于10个月前

这有帮助吗?