如何阻止超过3个线程到达sem_wait()?单向限3辆的道路信号量同步问题求解
现有代码的核心逻辑漏洞
- 准入判断无锁保护:
while (currActiveDir != direction[0] || numOfCarsOnRoad != 0)这行判断没有加互斥锁,多线程并发场景下,判断完到执行sem_wait的间隙,全局状态(通行方向、路上车辆数)可能已经被其他线程修改,大量不符合准入条件的线程会提前卡在sem_wait队列里,等sem有信号就直接进入,完全绕过了方向和轮次限制。 - 缺少轮次计数:没有限制每一轮同向车的准入数量,只要sem有空位、方向一致,不管是不是同一轮的车都能进入,自然会出现同方向连续通行N辆的情况,只有等所有堵在sem里的同方向车都走完才会切方向。
- 全局变量更新无锁:
carsGoingW/carsGoingE的增减、currActiveDir的初始化都没有加锁,存在并发读写的脏数据问题。
正确实现思路
核心要保证每一轮的准入判断是原子的,同一轮最多放3辆同向车,放满或者本轮没有更多同方向等待车辆就锁方向,等道路清空后切换方向。调整执行顺序:先拿mutex锁做准入判断,符合条件再执行准入操作,而不是先等sem再拿锁,避免sem队列堆积不符合条件的线程。
具体规则:
- 所有全局状态的读写都必须在持有mutex锁的场景下进行
- 车辆线程先拿mutex锁,判断当前是否满足准入条件:
- 通行方向和自己一致,且当前本轮已经进入的车辆数<3,直接准入,更新路上车辆数和本轮计数
- 通行方向和自己不一致,或者本轮已经满3辆,释放mutex锁,进入条件变量等待
- 车辆驶离道路时拿mutex锁更新计数,当路上车辆数为0时,切换通行方向,重置轮次计数,唤醒所有等待的车辆重新竞争准入
修改后的可运行代码
nr.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <string.h> #include <stdbool.h> #include "nr.h" pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; unsigned int numOfCarsOnRoad = 0; unsigned int carsGoingW = 0; unsigned int carsGoingE = 0; unsigned int currRoundCnt = 0; // 本轮已经进入的车辆数 unsigned long numOfCars = 0; char currActiveDir; // W/E bool currDirInitialized = false; void *crossBridge(void *i) { int id = *((int *)i); char direction[5]; char dir_char; pthread_mutex_lock(&mutex); if (rand() % 2 == 0) { strcpy(direction, "West"); dir_char = 'W'; carsGoingW++; } else { strcpy(direction, "East"); dir_char = 'E'; carsGoingE++; } if (!currDirInitialized) { currActiveDir = dir_char; currDirInitialized = true; } pthread_mutex_unlock(&mutex); // 准入等待 pthread_mutex_lock(&mutex); while (currActiveDir != dir_char || currRoundCnt >=3 || numOfCarsOnRoad >=3) { pthread_cond_wait(&cond, &mutex); } // 准入成功 currRoundCnt++; numOfCarsOnRoad++; printf("Car #%d going to the %s. Number of cars on the road = %d\n", id, direction, numOfCarsOnRoad); pthread_mutex_unlock(&mutex); sleep(1); // 模拟过桥 // 驶离更新状态 pthread_mutex_lock(&mutex); if (dir_char == 'W') carsGoingW--; else carsGoingE--; numOfCarsOnRoad--; printf("Car #%d crossed to the %s! Number of cars on the road = %d\n", id, direction, numOfCarsOnRoad); // 道路清空,切换方向重置轮次 if (numOfCarsOnRoad == 0) { currRoundCnt = 0; // 优先切有等待车辆的方向,避免饥饿 if (currActiveDir == 'W' && carsGoingE > 0) currActiveDir = 'E'; else if (currActiveDir == 'E' && carsGoingW > 0) currActiveDir = 'W'; // 唤醒所有等待线程重新竞争 pthread_cond_broadcast(&cond); } pthread_mutex_unlock(&mutex); free(i); pthread_exit(NULL); } void parseCarArg(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "-c") == 0) { if (++i < argc && strlen(argv[i]) > 0) numOfCars = strtol(argv[i], NULL, 10); if (numOfCars == 0) { perror("You must enter a number of cars > 0!\n"); exit(EXIT_FAILURE); } break; } } } int main(int argc, char *argv[]) { if (argc == 0) exit(EXIT_FAILURE); parseCarArg(argc, argv); srand(time(NULL)); pthread_t cars[numOfCars]; int i; for (i = 0; i < numOfCars; i++) { int *id = malloc(sizeof(int)); *id = i; if (pthread_create(&cars[i], NULL, crossBridge, id) != 0) { perror("Failed to create threads for the cars!\n"); exit(EXIT_FAILURE); } } for (i = 0; i < numOfCars; i++) pthread_join(cars[i], NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; }
nr.h
无需修改,和原代码一致即可。
内容的提问来源于stack exchange,提问作者Stelios Papamichail
相关产品推荐
相关产品推荐

