放弃 (2)

放弃 (2)

2024 南京大学《操作系统:设计与实现》
放弃 (2)

反思:状态机模型的隐含假设 (再一次)

“世界上只有一个状态机”

  • 没有其他任何人能 “干涉” 程序的状态
  • 课堂上常用的简化方法
    • 假设一段程序执行没有系统调用
    • 可以直接简化为一个原子的状态迁移 “计算”

编译器也做了同样的假设

  • 编译器会试图优化状态迁移,改变执行流
2024 南京大学《操作系统:设计与实现》
放弃 (2)

放弃程序按顺序执行的假设

共享内存推翻了编译器的假设

  • 但编译器依然会按照顺序执行优化代码
  • 否则几乎任何涉及共享内存的代码都变得 “不可优化”

程序的行为在并发编程下变得更难理解了

  • “顺序程序” 变得一点也不 “顺序” 了
2024 南京大学《操作系统:设计与实现》
放弃 (2)

例子:求和 (再次出现)

#define N 100000000
long sum = 0;

void T_sum() { for (int i = 0; i < N; i++) sum++; }

int main() {
    create(T_sum);
    create(T_sum);
    join();
    printf("sum = %ld\n", sum);
}

如果添加编译优化?

  • -O1: 100000000 😱😱
  • -O2: 200000000 😱😱😱
2024 南京大学《操作系统:设计与实现》
放弃 (2)

另一个例子

while (!flag);

“等另一个线程举起旗子,我再继续”?

2024 南京大学《操作系统:设计与实现》
放弃 (2)

控制执行顺序 🌶️

方法 1:插入 “不可优化” 代码

  • asm volatile ("" ::: "memory");
    • 告诉编译器其他线程可能写入内存

方法 2:标记变量 load/store 为不可优化

  • 使用 volatile 修饰变量
int volatile flag;
while (!flag);

以上都不是《操作系统》课推荐的方法

  • “一把大锁保平安”
2024 南京大学《操作系统:设计与实现》