一、猴子拿苹果问题
逛脉脉时,看到一网友遇到的面试题:有9个苹果,2只猴子。一个猴子每次拿2个苹果,一个猴子每次拿3个苹果。如果剩余的苹果数量不够猴子拿的数量,则停止拿苹果。请用多线程的方式模拟上面的描述。
看到问题的第一眼,觉得很有趣,脑海中第一个想到的就是通过信号量来实现,因为信号量是最适合做线程和进程状态同步了,而问题的考点就是线程同步。
恰好这两天刚好有在看信号量,所以很容易就想到通过信号量来实现这个问题。
其实原题说的是通过java多线程实现,但是我不会java,只能用c来写了。
为什么说考查的问题点是线程同步?因为题目要求是通过多线程来实现,如果不对线程状态制约,9个苹果,很容易在一瞬间就被一个猴子拿完,或许第二个线程还没开始苹果就已经拿完了。这明显不是面试官想看到的。所以不难想到考点就是如何协调两个猴子(线程)有序的拿桃子,有序的意思就是:你拿一次,我拿一次,要有次序的进行,直到把桃子拿完。如何控制线程之间你执行一次,我执行一次,肯定就要利用同步了。
二、匿名信号
匿名信号比较适用于线程间同步,因为它没有名字,所以多个进程间无法通过一个载体来共享它。只有在线程中,通过变量或者参数传递的方式来共享,所以适用于线程。
有名信号的用法参考:进程间通信之信号量。
匿名信号的原理也和有名信号一样,维持一个计数器,然后通过等待计数器的状态来继续往下执行。相关的函数:
#include <semaphore.h>
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 发送信号量
int sem_post(sem_t *sem);
// 等待信号量
int sem_wait(sem_t *sem);
// 关闭信号量
int sem_close(sem_t *sem);
四个函数中,下面三个和有名信号量中的一样,唯一有区别的是第一个初始化信号量函数。匿名信号量无需传入信号量的名字,直接传入信号量对象就可以初始化了。后面的两个参数也都是一样,一个代表共享属性,一个代表默认值。
三、实现猴子拿苹果问题
两个猴子,肯定是需要两个线程,线程中辨别是哪个猴子肯定要传入猴子的参数,因此先实现一个猴子信息的对象,传入线程参数:
// 每个猴子的信息
struct monkey_info {
int id; // 猴子的ID
int cnt; // 每次拿苹果的数量
sem_t *my; // 自己的信号量
sem_t *other; // 另外一个猴子的信号量
};
为什么需要两个信号量呢,逻辑是:我去拿猴子,拿完通知你拿,你去拿猴子,我等在这里,等你拿完通知我拿。一个是等待自己的信号量,一个是通知另外猴子的信号量,所以需要两个。
线程函数实现:
// 苹果总量
static int s_apple_cnt = 9;
void *get_apple(void *ptr) {
struct monkey_info *monkey = ptr;
if (monkey == NULL)
return NULL;
while (1) {
// 等待信号量
sem_wait(monkey->my);
if (s_apple_cnt < monkey->cnt) { // 当前的苹果数量不足以给到当前猴子
printf("monkey %d: apple has't enough! total: %d, need: %d\n", monkey->id, s_apple_cnt, monkey->cnt);
// 退出前,通知另外猴子继续拿
sem_post(monkey->other);
break;
} else {
// 拿苹果,减库存
s_apple_cnt -= monkey->cnt;
printf("monkey %d: get %d apple! remaining apples: %d\n", monkey->id, monkey->cnt, s_apple_cnt);
}
// 通知另外猴子
sem_post(monkey->other);
}
return NULL;
}
主函数实现:
int main() {
sem_t sem1, sem2;
pthread_t tid1, tid2;
// 初始化两个猴子的状态
struct monkey_info monkey1 = {1, 2, &sem1, &sem2};
struct monkey_info monkey2 = {2, 3, &sem2, &sem1};
// 初始化信号
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);
// 创建线程
pthread_create(&tid1, NULL, get_apple, (void *)&monkey1);
pthread_create(&tid2, NULL, get_apple, (void *)&monkey2);
// 等待线程退出
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 退出信号量
sem_close(&sem1);
sem_close(&sem2);
return 0;
}
执行结果:
过程描述:
- 第一个猴子拿2个苹果,剩余7个苹果。
- 第二个猴子拿3个苹果,剩余4个苹果。
- 第一个猴子拿2个苹果,剩余2个苹果。
- 第二个猴子想要拿3个苹果,但是苹果不够了,退出。
- 第一个猴子拿两个苹果,苹果被拿完。
- 第一个猴子想要拿2个苹果,但是苹果不够了,退出。
此处评论已关闭