牛奶与计算机同步

以下内容整理自课程“计算机系统概论”。

假设 A 和 B 是舍(ren)友(ji)。他们中任何一人发现冰箱里没有牛奶时,都会出门去买牛奶。

这个过程中的基本操作(atomic operation) 包括:检查冰箱里是否有牛奶(milk),买牛奶(buy)。如果用代码写,相当于 if(milk == 0) buy;

那么存在这样的情况:A milk, B milk, A buy, B buy。

然后冰箱里的牛奶就太多了!

可能你会觉得 A 和 B 应该碰上,但这就是多线程程序执行时遇到的问题:不同线程之间的命令可能是无序的。一个正确的多线程程序必须考虑到这一点。

好吧,A 和 B 思考了一下,决定用 note 来传递信息。这时操作又多了两种:留下标签(leave),检查标签(note),删除标签(remove)。

这里的 note 在线程中称作“锁”,当然锁不只是 note,一切阻止线程做某件事的机制都叫做锁。锁会导致线程需要等到;一个重要的规律是,任何正确的同步都存在等待。

现在他们都采用这样的策略:milk, note, leave, buy, remove。或者用代码写:

if(milk == 0)
	if(note == 0)
		leave;
		buy;
		remove;

对吗?补兑!存在这样的情况:

A:  if(milk == 0)
A:  	if(note == 0)
B:	if(milk == 0)
B:		if(note == 0)
A:			leave;
A:			buy;
A:			remove;
B:			leave;
B:			buy;
B:			remove;

太糟糕了!当然,确实有限的好了一些:在某些情况下不会出错了。

如果将策略换成 leave, milk, note, buy, remove 呢?对于人可能够了,但是对计算机而言,这就是自己把自己锁上了,没救了。

那他们只好继续加机制,A 和 B 分别创建了自己的标签。现在 A 的策略是 leave-A, milk, note-B, buy, remove-A,B 的策略是 leave-B, milk, note-A, buy, remove-B。

对吗?还是补兑!看这个情况:

A:  leave-A;
B:	leave-B;
B:	if(milk == 0) // true
B:		if(note-A == 0) // false
B:			buy; // non-execution
A:	if(milk == 0) // true
A:		if(note-B == 0) // false
A:			buy;
B:  remove-B;
A:	remove-A;

买不到牛奶了!

一种可能的解法是,A 采用

leave-A;
while(note-B == 1) {
	do nothing;
	// maybe you can chat with the fridge. It's really cool!
}
if(milk == 0) 
	buy;
remove-A;

而 B 仍然采用之前的方案。这里我们就设计了一个等待。

事实上这相当于设计了一个临界区(critical section),在 A 进行 milk, buy 的时候,不会被 B 打断。

不过,这个做法看起来似乎有点太复杂了,它的正确性也不是直观的。另外还有两个问题:A 和 B 的策略不同;A 等待的时候浪费了时间,对于 CPU 来说就是浪费了算力(称为 busy-waiting)。

好吧,现在 A 和 B 都想不出来办法了,能不能借助一些“外力”呢?也就是说,硬件已经这样了,有没有办法在更高的层面上避免这种情况?

有的兄弟,有的!

现在 A 和 B 去买了一把锁,他们约定,如果谁出去买牛奶了,就把冰箱锁上。好了,一切都解决了。

不过,计算机应该怎么造锁呢?这就不是本文讨论的内容了。

posted @ 2025-04-15 19:38  by_chance  阅读(63)  评论(0)    收藏  举报