当前位置: 首页 > news >正文

C语言实现 操作系统 经典的进程同步问题(2)

哲学家进餐问题

哲学家进餐问题是一个经典的同步问题,涉及多个哲学家试图同时用餐,但每个哲学家左右两边只有一把叉子。为了避免死锁和饥饿,可以使用记录型信号量(也称为计数信号量)来管理叉子的使用。

1、利用记录型信号量解决哲学家进餐问题

(1)信号量初始化:使用 sem_init 初始化每把叉子的信号量,初始值为1,表示可用。

哲学家线程:每个哲学家线程尝试拿起左边的叉子(sem_trywait),如果失败则继续思考。如果成功拿起左边的叉子,再尝试拿起右边的叉子。如果失败,则放下左边的叉子并继续思考。如果成功拿起两把叉子,则哲学家开始吃饭,吃完后放下两把叉子。

输出控制:使用互斥锁 print_mutex 控制输出顺序,避免输出混乱。

线程创建和等待:创建哲学家线程,并使用 pthread_join 等待所有线程完成(实际上这是一个无限循环程序,所以不会真正退出)。

资源清理:销毁信号量和互斥锁。

#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  
#include <semaphore.h>  
#include <unistd.h>  #define NUM_PHILOSOPHERS 5  
#define THINK_TIME 1  
#define EAT_TIME 2  sem_t forks[NUM_PHILOSOPHERS];  // 每把叉子一个信号量  
pthread_mutex_t print_mutex;    // 控制输出顺序的互斥锁  void *philosopher(void *arg) {  int id = *((int *)arg);  int left_fork = id;  int right_fork = (id + 1) % NUM_PHILOSOPHERS;  while (1) {  // 思考  printf("Philosopher %d is thinking.\n", id);  sleep(THINK_TIME);  // 尝试拿起左边的叉子  if (sem_trywait(&forks[left_fork]) == -1) {  // 拿起左边的叉子失败,继续思考  continue;  }  // 尝试拿起右边的叉子  if (sem_trywait(&forks[right_fork]) == -1) {  // 拿起右边的叉子失败,放下左边的叉子并继续思考  sem_post(&forks[left_fork]);  continue;  }  // 拿起两把叉子,开始吃饭  printf("Philosopher %d is eating.\n", id);  sleep(EAT_TIME);  printf("Philosopher %d has finished eating.\n", id);  // 放下叉子  sem_post(&forks[left_fork]);  sem_post(&forks[right_fork]);  }  pthread_exit(NULL);  
}  int main() {  pthread_t threads[NUM_PHILOSOPHERS];  int ids[NUM_PHILOSOPHERS];  // 初始化信号量,每把叉子的初始值为1,表示可用  for (int i = 0; i < NUM_PHILOSOPHERS; i++) {  sem_init(&forks[i], 0, 1);  }  // 初始化互斥锁,用于控制输出顺序  pthread_mutex_init(&print_mutex, NULL);  // 创建哲学家线程  for (int i = 0; i < NUM_PHILOSOPHERS; i++) {  ids[i] = i;  pthread_create(&threads[i], NULL, philosopher, &ids[i]);  }  // 等待所有线程完成(实际上,这是一个无限循环程序,所以这里不会退出)  for (int i = 0; i < NUM_PHILOSOPHERS; i++) {  pthread_join(threads[i], NULL);  }  // 销毁信号量和互斥锁  for (int i = 0; i < NUM_PHILOSOPHERS; i++) {  sem_destroy(&forks[i]);  }  pthread_mutex_destroy(&print_mutex);  return 0;  
}

注意:

  • sem_trywait 是非阻塞的,如果信号量的值大于0,则减一并返回,否则立即返回-1并设置errno为EAGAIN。
  • 使用 pthread_join 等待线程实际上在这个例子中是不必要的,因为哲学家线程是无限循环的。如果要终止程序,可以添加额外的控制逻辑。

(2)init_random():初始化随机数生成器。

think(int philosopherNumber):模拟哲学家思考的过程,打印消息并随机睡眠一段时间。

eat(int philosopherNumber):模拟哲学家吃饭的过程,打印消息并随机睡眠一段时间。

*philosopher(void *arg):哲学家的线程函数,包含无限循环,不断执行思考和吃饭的过程。在拿起叉子前使用互斥锁保护,成功后释放互斥锁,吃饭后放下叉子。

#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  
#include <semaphore.h>  
#include <unistd.h> // 包含sleep函数所需的头文件  
#include <time.h>  #define PHILOSOPHERS_COUNT 5  sem_t forks[PHILOSOPHERS_COUNT];  
sem_t mutex;  // 初始化随机数生成器  
void init_random() {  srand(time(NULL));  
}  void think(int philosopherNumber) {  printf("Philosopher %d is thinking.\n", philosopherNumber);  // 模拟思考时间  int sleepTime = rand() % 5 + 1;  sleep(sleepTime);  
}  void eat(int philosopherNumber) {  printf("Philosopher %d is eating.\n", philosopherNumber);  // 模拟吃饭时间  int sleepTime = rand() % 5 + 1;  sleep(sleepTime);  
}  void *philosopher(void *arg) {  int philosopherNumber = *((int *)arg);  while (1) { // 注意:这里的无限循环可能导致程序永远不退出  think(philosopherNumber);  // 使用互斥锁保护对叉子的访问  sem_wait(&mutex);  // 尝试拿起左右两边的叉子  int leftFork = philosopherNumber;  int rightFork = (philosopherNumber + 1) % PHILOSOPHERS_COUNT;  sem_wait(&forks[leftFork]);  sem_wait(&forks[rightFork]);  // 释放互斥锁,因为已经成功拿起了两把叉子  sem_post(&mutex);  eat(philosopherNumber);  // 放下叉子  sem_post(&forks[leftFork]);  sem_post(&forks[rightFork]);  }  return NULL;  
}  int main() {  pthread_t philosophers[PHILOSOPHERS_COUNT];  int philosopherNumbers[PHILOSOPHERS_COUNT];  // 初始化随机数生成器  init_random();  // 初始化信号量  sem_init(&mutex, 0, 1);  for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {  sem_init(&forks[i], 0, 1);  }  // 创建哲学家线程  for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {  philosopherNumbers[i] = i;  pthread_create(&philosophers[i], NULL, philosopher, &philosopherNumbers[i]);  }  // 注意:这里使用pthread_join会导致程序永远等待,因为哲学家线程是无限循环的  // 如果需要程序退出,应该实现某种退出机制,比如使用全局变量和条件变量  for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {  pthread_join(philosophers[i], NULL);  }  // 清理资源(在实际应用中,应该在确保所有线程都正确退出后再进行)  for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {  sem_destroy(&forks[i]);  }  sem_destroy(&mutex);  // 注意:由于使用了无限循环,下面的代码将不会被执行  // 这里只是为了展示如何清理资源  // 在实际应用中,应在程序退出前确保所有线程都正确终止  return 0;  
}

注意:由于while (1)循环的存在,pthread_join和信号量销毁的代码在实际应用中不会被执行。在实际应用中,您需要实现一种机制来安全地终止线程并清理资源。这通常涉及到使用全局变量、条件变量或其他线程间通信机制来协调线程的终止。 

2、利用AND信号量机制解决哲学家进餐问题

(1)AND信号量要求线程(在本例中为哲学家)同时获取多个资源(叉子)才能继续执行。然而,标准的POSIX信号量API并不直接支持AND信号量,但我们可以通过使用互斥锁(mutex)和计数信号量(counting semaphore)的组合来模拟这种机制。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>#define PHILOSOPHERS_COUNT 5
#define THINK_TIME 1 // 思考时间(秒)
#define EAT_TIME 1  // 吃饭时间(秒)sem_t forks[PHILOSOPHERS_COUNT]; // 每只叉子一个信号量
pthread_mutex_t mutex;          // 互斥锁,用于保护对退出标志的访问
bool should_exit = false;        // 退出标志// 初始化随机数生成器(虽然在这个示例中未直接使用 rand,但保留以备将来需要)
void init_random() {srand(time(NULL));
}// 哲学家线程函数
void *philosopher(void *arg) {int philosopher_number = *((int *)arg);while (!should_exit || /* 另一个条件,用于确保在退出前完成当前循环 */(should_exit && (philosopher_number % 2 == 0 || /* 可选的:让偶数编号的哲学家先完成 */(sem_trywait(&forks[philosopher_number]) == 0 && sem_trywait(&forks[(philosopher_number + 1) % PHILOSOPHERS_COUNT]) == 0)))) {// 思考printf("Philosopher %d is thinking.\n", philosopher_number);sleep(THINK_TIME);// 尝试拿起左右两边的叉子(模拟 AND 信号量)pthread_mutex_lock(&mutex); // 保护对叉子信号量的操作if (sem_trywait(&forks[philosopher_number]) == 0 &&sem_trywait(&forks[(philosopher_number + 1) % PHILOSOPHERS_COUNT]) == 0) {// 成功拿起两把叉子pthread_mutex_unlock(&mutex); // 释放互斥锁,因为已经成功拿起了叉子// 吃饭printf("Philosopher %d is eating.\n", philosopher_number);sleep(EAT_TIME);// 放下叉子sem_post(&forks[philosopher_number]);sem_post(&forks[(philosopher_number + 1) % PHILOSOPHERS_COUNT]);} else {// 未能同时拿起两把叉子,释放互斥锁并继续思考pthread_mutex_unlock(&mutex);}// 检查退出标志pthread_mutex_lock(&mutex);if (should_exit) {pthread_mutex_unlock(&mutex);break; // 直接跳出循环}pthread_mutex_unlock(&mutex);}return NULL;
}int main() {pthread_t philosophers[PHILOSOPHERS_COUNT];int philosopher_numbers[PHILOSOPHERS_COUNT];// 初始化随机数生成器(虽然在这个示例中未直接使用)init_random();// 初始化信号量和互斥锁pthread_mutex_init(&mutex, NULL);for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {sem_init(&forks[i], 0, 1); // 初始值为 1,表示叉子可用}// 创建哲学家线程for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {philosopher_numbers[i] = i;pthread_create(&philosophers[i], NULL, philosopher, &philosopher_numbers[i]);}// 让哲学家们运行一段时间,然后设置退出标志sleep(10); // 这里只是为了演示,实际中可能使用其他条件来触发退出pthread_mutex_lock(&mutex);should_exit = true;pthread_mutex_unlock(&mutex);// 等待哲学家线程退出for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {pthread_join(philosophers[i], NULL);}// 清理资源for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {sem_destroy(&forks[i]);}pthread_mutex_destroy(&mutex);return 0;
}

(2)sem_t mutex;:用于保护对共享资源(如state数组和meals_eaten数组)的访问。

sem_t s[N];:表示左右叉子的可用性。如果s[i]为0,则第i位哲学家的右叉子(或第(i-1)%N位哲学家的左叉子)不可用。

sem_t s_try[N];:用于在哲学家尝试获取叉子时同步。

int state[N];:表示每位哲学家的当前状态(思考、饥饿、就餐)。

int meals_eaten[N];:记录每位哲学家吃过的饭的数量。

pthread_cond_t cond[N];:条件变量,用于在哲学家无法获取叉子时等待

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdbool.h>#define N 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2
#define MAX_MEALS 10 sem_t mutex;
sem_t s[N];
sem_t s_try[N];
int state[N];
int meals_eaten[N];pthread_cond_t cond[N];void *philosopher(void *num);
void take_forks(int);
void put_forks(int);
void test(int);void debug_print(int philosopher_num, const char *message) {printf("Philosopher %d: %s\n", philosopher_num, message);
}int main() {pthread_t thread_id[N];sem_init(&mutex, 0, 1);for (int i = 0; i < N; i++) {sem_init(&s[i], 0, 0);sem_init(&s_try[i], 0, 0);meals_eaten[i] = 0;pthread_cond_init(&cond[i], NULL);}for (int i = 0; i < N; i++) {pthread_create(&thread_id[i], NULL, philosopher, (void *)(long long)i);}for (int i = 0; i < N; i++) {pthread_join(thread_id[i], NULL);}return 0;
}void *philosopher(void *num) {long long int i = (long long int)(num);for (int meal = 0; meal < MAX_MEALS; meal++) {// 思考{sem_wait(&mutex);state[i] = THINKING;sem_post(&mutex);debug_print(i, "Thinking");}// 尝试获取叉子take_forks((int)i);// 吃饭{sem_wait(&mutex);state[i] = EATING;sem_post(&mutex);debug_print(i, "Eating");}// 放下叉子put_forks((int)i);}return NULL;
}void take_forks(int i) {sem_wait(&mutex);state[i] = HUNGRY;debug_print(i, "Hungry");test(i);if (state[i]!= EATING) {pthread_cond_wait(&cond[i], &mutex);}sem_post(&mutex);sem_wait(&s_try[i]);sem_wait(&s[i]);
}void put_forks(int i) {sem_wait(&mutex);state[i] = THINKING;debug_print(i, "Put down forks");meals_eaten[i]++;test((i + 4) % N);test((i + 1) % N);sem_post(&mutex);sem_post(&s[(i + 4) % N]);sem_post(&s[(i + 1) % N]);pthread_cond_signal(&cond[(i + 4) % N]);pthread_cond_signal(&cond[(i + 1) % N]);
}void test(int i) {sem_wait(&mutex);if (state[i] == HUNGRY && state[(i + 4) % N]!= EATING && state[(i + 1) % N]!= EATING) {state[i] = EATING;sem_post(&s_try[i]);sem_post(&s[i]);pthread_cond_signal(&cond[i]);}sem_post(&mutex);
}

注意:

  • 代码中使用了信号量和条件变量来同步对共享资源的访问,以避免竞态条件和死锁。
  • 通过mutex信号量保护对state数组的访问,确保在检查和修改哲学家状态时不会发生数据竞争。
  • 通过s[i]s_try[i]信号量以及条件变量cond[i]来协调哲学家获取和释放叉子的过程。
  • 代码中的% N操作确保了索引在有效范围内循环。
http://www.lryc.cn/news/450547.html

相关文章:

  • 有效的字母异位词【字符串哈希】
  • 如何选择与运用工具提升工作效率的秘密指南
  • Spring系列 AOP实现过程
  • C语言 getchar 函数完全解析:掌握字符输入的关键
  • Docker安装mysql8并配置主从复制
  • 快手:数据库升级实践,实现PB级数据的高效管理|OceanBase案例
  • 基于Node.js+Express+MySQL+VUE实现的计算机毕业设计共享单车管理网站
  • 人工智能辅助的神经康复
  • KKT实际运用 -MATLAB
  • php在线相册
  • Xcode手动安装SDK模拟器
  • Docker安装consul + go使用consul + consul知识
  • JWT 漏洞 - 学习手册
  • HTML【知识改变命运】03font 字体标签
  • 集师专属知识付费小程序搭建 心理咨询小程序搭建
  • https://www.aitoolpath.com/ 一个工具数据库,目前储存了有2000+各种工具。每日更新
  • 科技的成就(六十三)
  • 浅谈抗量子密码学:保护未来的数字安全
  • 10款物联网开源嵌入式操作系统对比分析
  • Elasticsearch深度攻略:核心概念与实践应用
  • TLS详解
  • 正则表达式中的特殊字符
  • EP42 公告详情页
  • 游戏找不到xinput1_3.dll的原因及解决方法
  • 防反接电路设计
  • SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
  • 毕业设计选题:基于ssm+vue+uniapp的购物系统小程序
  • 【动态规划-最长公共子序列(LCS)】力扣583. 两个字符串的删除操作
  • 【分布式微服务云原生】8分钟探索RPC:远程过程调用的奥秘与技术实现
  • Linux操作系统中Redis