实验五、进程同步问题实现

一、实验目的

利用实验四提供的方法和例子,解决进程同步相关问题,例如:生产者消费者问题,哲学家进餐等问题。

二、实验环境

  • 硬件环境:计算机一台,局域网环境;
  • 软件环境:Linux Ubuntu 操作系统,gcc 编译器;

三、实验内容和步骤

  1. 生产者消费者问题
    一组生产者进程向一组消费者进程提供产品,两类进程共享一个由n个缓冲区组成的有界缓冲池,生产者进程向空缓冲池中投放产品,消费者进程从放有数据的缓冲池中取得产品并消费掉。只要缓冲池未满,生产者进程就可以把产品送入缓冲池;只要缓冲池未空,消费者进程便可以从缓冲池中取走产品。但禁止生产者进程向满的缓冲池再输送产品,也禁止消费者进程从空的缓冲池中提取产品。为了防止对缓冲池重复操作,故规定在任何时候,只有一个主体可以访问缓冲池。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>

#define PRODUCER_NUM 5 // 生产者数目
#define CONSUMER_NUM 5 // 消费者数目
#define POOL_SIZE 11 // 缓冲池大小

int pool[POOL_SIZE]; // 缓冲区
int head = 0; // 缓冲池读取指针
int rear = 0; // 缓冲池写入指针
sem_t room_sem; // 同步信号信号量,表示缓冲区有可用空间
sem_t product_sem; // 同步信号量,表示缓冲区有可用产品
pthread_mutex_t mutex;

void *producer_fun(void *arg) {
while (1) {
sleep(1);
sem_wait(&room_sem);
pthread_mutex_lock(&mutex);
// 生产者往缓冲池中写入数据
pool[rear] = 1;
rear = (rear + 1) % POOL_SIZE;
printf("producer %d write to pool\n", (long) arg);
printf("pool size is %d\n", (rear - head + POOL_SIZE) % POOL_SIZE);
pthread_mutex_unlock(&mutex);
sem_post(&product_sem);
}
}

void *consumer_fun(void *arg) {
while (1) {
int data;
sleep(10);
sem_wait(&product_sem);
pthread_mutex_lock(&mutex);
// 消费者从缓冲池读取数据
data = pool[head];
head = (head + 1) % POOL_SIZE;
printf("consumer %d read from pool\n", (long) arg);
printf("pool size is %d\n", (rear - head + POOL_SIZE) % POOL_SIZE);
pthread_mutex_unlock(&mutex);
sem_post(&room_sem);
}
}

int main() {
int i;
pthread_t producer_id[PRODUCER_NUM];
pthread_t consumer_id[CONSUMER_NUM];
pthread_mutex_init(&mutex, NULL); // 初始化互斥量
int ret = sem_init(&room_sem, 0, POOL_SIZE - 1); // 初始化信号量room_sem为缓冲池大小
if (ret != 0) {
printf("sem_init error");
exit(0);
}
ret = sem_init(&product_sem, 0, 0); // 初始化信号量product_sem为0,开始时缓冲池中没有数据
if (ret != 0) {
printf("sem_init error");
exit(0);
}
for (i = 0; i < PRODUCER_NUM; i++) {
//创建生产者线程
ret = pthread_create(&producer_id[i], NULL, producer_fun, (void *) i);
if (ret != 0) {
printf("producer_id error");
exit(0);
}
//创建消费者线程
ret = pthread_create(&consumer_id[i], NULL, consumer_fun, (void *) i);
if (ret != 0) {
printf("consumer_id error");
exit(0);
}
}
for (i = 0; i < PRODUCER_NUM; i++) {
pthread_join(producer_id[i], NULL);
pthread_join(consumer_id[i], NULL);
}
exit(0);
}
  1. 哲学家进餐问题
    有5位哲学家倾注毕生精力用于思考和吃饭,他们围坐在一张圆桌旁,在圆桌上有5个碗和5支筷子。每位哲学家的行为通常是思考,当其感到饥饿时,便试图取其左右最靠近他的筷子进餐。只有他拿到两支筷子后才能进餐,进餐完毕后,释放两支筷子并继续思考。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

// 规定:只有当哲学接的左右两只筷子均处于可用状态时,才允许他拿起筷子。
// 这样可以避免他们同时拿起筷子就餐,导致死锁。
#define N 5 // 哲学家人数
#define T_EAT 5
#define T_THINK 5
#define N_ROOM 4 //同一时间只允许4人用餐
#define left(phi_id) (phi_id+N-1)%N
#define right(phi_id) (phi_id+1)%N

enum {
think, hungry, eat
} phi_state[N];
sem_t chopstick[N];
sem_t room;

void thinking(int id) {
sleep(T_THINK);
printf("philosopher[%d] is thinking...\n", id);
}

void eating(int id) {
sleep(T_EAT);
printf("philosopher[%d] is eating...\n", id);
}

void take_forks(int id) {
// 获取左右两边的筷子
// printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
sem_wait(&chopstick[left(id)]);
sem_wait(&chopstick[right(id)]);
// printf("philosopher[%d] take_forks...\n", id);
}

void put_down_forks(int id) {
printf("philosopher[%d] is put_down_forks...\n", id);
sem_post(&chopstick[left(id)]);
sem_post(&chopstick[right(id)]);
}

void *philosopher_work(void *arg) {
int id = *(int *) arg;
printf("philosopher init [%d] \n", id);
while (1) {
thinking(id);
sem_wait(&room);
take_forks(id);
sem_post(&room);
eating(id);
put_down_forks(id);
}
}

int main() {
pthread_t phiTid[N];
int i;
int err;
int *id = (int *) malloc(sizeof(int) * N);

for (i = 0; i < N; i++) {
if (sem_init(&chopstick[i], 0, 1) != 0) {
printf("init forks error\n");
}
}

sem_init(&room, 0, N_ROOM);

for (i = 0; i < N; ++i) {
// printf("i ==%d\n", i);
id[i] = i;
err = pthread_create(&phiTid[i], NULL, philosopher_work, (void *) (&id[i])); //这种情况生成的thread id是0,1,2,3,4
if (err != 0)
printf("can't create process for reader\n");
}

while (1);

for (i = 0; i < N; i++) {
err = sem_destroy(&chopstick[i]);
if (err != 0) {
printf("can't destory semaphore\n");
}
}
exit(0);
return 0;
}
  1. 和尚打水问题
    某寺庙,有小和尚,老和尚若干。有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容10桶水,水取自同一井中。水井径窄,每次中能容下一个桶取水。水桶总数为3个。每人一次取缸水仅为1桶,且不可同时进行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define P sem_wait
#define V sem_post
#define mutex1 &muteX1
#define mutex2 &muteX2
#define amount &Amount
#define empty &Empty
#define full &Full

#define N 10

sem_t muteX1, muteX2;
sem_t Amount, Empty, Full;

int littleMonkCount = 0;
int BigMonkCount = 0;
int fullcount = 0;
int sum = 50;

k(void *p) {
while (sum) {
sum--;
P(empty); //先看水缸里是否满了,没满才能够打水,可以打水的容量-1
littleMonkCount++;
P(amount); //有没有打水桶可以用,有就可以继续打水,可用水桶-1
P(mutex1); //没有其他人打水才能打水
//井边提水
printf("第%d个小和尚在水井提水\n", littleMonkCount);
V(mutex1); //打完水后释放资源
P(mutex2); //回到水缸旁等待倒水,如果此时没有其他小和尚倒水和大和尚取水,即可倒水
//小和尚在水缸旁提水
printf("水缸已有%d桶水,第%d个小和尚在水缸旁倒水\n", fullcount, littleMonkCount);
fullcount++;
V(mutex2); //倒完水后走开
V(amount); //放下水桶,可用水桶+1
littleMonkCount--;
V(full); //水缸里水的桶数加1
}
}

void *BigMonk(void *p) {
int myFull;
while (sum) {
sum--;
P(full); //如果有水缸里水,大和尚就找水桶准备打水,没有则在旁边等候
BigMonkCount++;
P(amount); //大和尚看到有水了去拿水桶,没有水桶则等待
P(mutex2); //水缸旁有人则等待,没有则开始打水
printf(" 水缸已有%d桶水,第%d个大和尚在水缸旁提水\n", fullcount, BigMonkCount);
fullcount--;
V(mutex2); //大和尚提完水后离去
V(amount); //放下水桶
BigMonkCount--;
V(empty); //可以打水的数量+1
}
}

int main() {
int i, j;
//初始化资源个数
sem_init(mutex1, 0, 1); //互斥锁默认为1
sem_init(mutex2, 0, 1); //互斥锁默认为1
sem_init(amount, 0, 3); //开始时有3个水桶供使用
sem_init(full, 0, 0); //开始时水缸里没有水,设置为0
sem_init(empty, 0, 10); //开始时水缸里最多能装10桶水,设置为10

//创建多个大和尚进程
for (i = 1; i <= 4; i++) {
pthread_t i;
pthread_create(&i, NULL, BigMonk, NULL);
}

//创建多个小和尚进程
for (j = 1; j <= 10; j++) {
pthread_t j;
pthread_create(&j, NULL, LittleMonk, NULL);
}

//观察结果后关闭进程
getchar();
pthread_exit(0);
return 0;
}

四、实验总结

进程同步是一个操作系统级别的概念,是在多道程序的环境下,存在着不同的制约关系,为了协调这种互相制约的关系,实现资源共享和进程协作,从而避免进程之间的冲突,引入了进程同步。 进程同步也是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。
互斥是指当一个进程进入临界区使用临界资源的时候,另外一个进程必须等待,当占用临界资源的进程退出临界区后,另外一个进程才允许去访问此临界资源。


实验五、进程同步问题实现
https://flowerdown.org/posts/20230603-150753.html
作者
Unrealfeather
发布于
2023年6月3日
许可协议