操作系统 线程同步 基础知识 (一)

6.2 临界区问题

临界区问题的解答必须满足以下三项要求:

1. 互斥. 不能有两个线程同时在临界区内执行

2. 前进. 当临界区为空, 而一个线程希望进入临界区时, 该线程进入临界区

3. 有限等待. 一个线程从申请进入临界区到真正进入临界区这段时间不能无限长.

6.3 Peterson 算法

一种软件的方法解决死锁问题, 但也并不能总是解决死锁问题

定义两个变量, 分别为 flag[0/1], turn. flag 表示哪个线程有进入临界区的意愿, turn 表示哪个线程

while(true) {
	flag[i] = true;
	turn = j;
	
	while(flag[j] && turn == j); // let j run first
	// cirtical section

	flag[i] = false;
	// remainer section
}

证明满足上面 3 个要求

1. 互斥. 假设两个线程同时进入临界区, 那么 flag[i] == flag[j] == 1. 当 turn = j 时, 线程 i 在自旋, 不可能进去.

2. 前进. 当线程 j 没有意愿进入临界区时, flag[j] = false; 当 i 有意愿进去时, 直接就进入了.

3. 有限等待. 首先申请临界区的线程进入临界区, 一个线程至多等待另一个线程在临界区执行一次, 满足有限等待.

6.4 硬件同步.

使用原子函数 setAndSwap()

void getAndSet(bool var) {
	swap(lock, var);
}

上面的代码核心是 getAndSet 必须时原子操作. 当 lock 为 false 时, 线程上锁进入临界区. 当 lock 为 true 时, 那么 getAndSet 无限循环, 直到 lock 为 false;

6.5 信号量与死锁

acquire() {
	value --;
	if(value < 0) {
		add this process to list
		block;
	}
}

release() {
	value ++;
	if(value <= 0) {
		remove a process P from list
		wakeup(P);
	}
}

当 value 为负时, value 的绝对值对应被阻塞线程的数目.

死锁

S.acquire();         Q.acquire();
Q.acquire();         S.acquire();

...			...

S.release();         Q.release();
Q.release();         S.release();

上面代码中, P0 执行 S.acquire(), P1 执行 Q.acquire, 然后 P0 执行 Q.acquire, 最后 P1 S.acquire.  

然后... 就死锁了

6.6 经典同步问题

1. 有限缓冲区问题

生成者通过 insert 函数向缓冲区内添加 item, 消费者通过 remove 函数在缓冲区内删除 item

信号量有 3 个, 分别为 empty, full, mutex. empty 表示缓冲区内的空格位置, insert 时需要检查缓冲区是否为空, remove 时需要检查缓冲区是否含有元素. mutex 提供对缓冲区的互斥访问

semaphore empty, full, mutex;

// init empty = size, full = 0, mutex = 1;

// java code
public void insert(Object item) {
	empty.acquire();
	mutex.acquire();

	buffer[in] = item;
	in = (in+1) % BUFFER_SIZE;

	mutex.release();
	full.release();
}

public Object remove() {
	full.acquire();
	mutex.acquire();

	Object item = buffer[out];
	out = (out + 1) % BUFFER_SIZE;

	mutex.release();
	empty.release();

	return item;
}

  

注意, empty, full 都必须使用信号量来表示其剩余个数, 不能用 if 代替

2. 读者写者问题

// writer may starve

void read() {
	while(true) {
		mutext.acquire();
		readCount ++;
		if(readCount == 1) {
			writeLock.acquire();
		}
		mutext.release();

		do reading

		mutext.acquire();
		readCount --;
		if(readCount == 0) {
			writeLock.release();
		}
		mutext.release();
	}
}

void write() {
	writeLock.acquire();
	do writing
	writeLock.release();
}

  

6.7 管程

管程将 acquire, release 这些操作封装起来了. 管程确保一次只有一个进程能在管程内活动.

管程内的条件遍历有两个操作:

wait() 挂起调用进程并释放管程, 直至另一个进程在条件变量上执行 signal()

signal() 假如有因条件变量被挂起的线程, 那么释放之, 否则什么也不做.

管程实现生产者消费者问题

// full means that no space for new added item
// empty means that no item for consumer
monitor ProducerConsumer {
	int itemCount;
	condition full;
	condition empty;

	procedure add(item) {
		if(itemCount == BUFFER_SIZE) {
			wait(full);
		}

		putItemIntoBuffer(item);
		itemCount ++;

		if(itemCount == 1) {
			signal(empty);
		}
	}

	procedure remove() {
		if(itemCount == 0) {
			wait(empty);
		}

		removeItemFromBuffer();
		itemCount --;

		if(itemCount == BUFFER_SIZE-1)
			signal(full);
	}
}

管程示意图

原文地址:https://www.cnblogs.com/zhouzhuo/p/3627139.html