信号量(Semaphore)未正确锁定:线程执行结果不符预期求助
线程临界区保护后输出异常的问题解决
问题场景
创建了两个线程:
- thread1执行
write_ticket函数:通过信号量锁定临界区,将ticketnumber设为5000,休眠1秒后打印该值 - thread2执行
ticketIncrease函数:同样用信号量锁定临界区,将ticketnumber自增1后打印
预期输出是先打印5000,再打印5001,但实际两次都打印5001;把sleep替换成打印语句后,问题消失。
问题原因
线程函数签名不符合pthread要求(核心问题)
pthread库规定线程入口函数必须是void* (*)(void*)类型,但你的两个线程函数都是void返回类型。这种类型不匹配会触发未定义行为——比如栈帧被破坏、信号量的sem_post操作未正确执行,直接导致临界区的互斥保护失效,线程间的操作顺序彻底混乱。共享变量未加volatile修饰(潜在优化干扰)
全局变量ticketnumber被多线程读写,但没加volatile修饰。编译器可能会把这个变量的值缓存到寄存器里,导致某个线程无法及时看到其他线程对变量的修改,进一步加剧结果的不确定性。线程调度顺序不确定(非直接原因)
pthread_create只是创建线程,操作系统的线程调度顺序是随机的,先创建的线程未必先执行。但正常情况下信号量会保证临界区互斥,只是前面的错误让信号量失去了作用。
修正后的代码
#include <stdio.h> #include <semaphore.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> // 加volatile防止编译器优化,保证线程间变量可见性 volatile int ticketnumber = 0; sem_t sem; // 修正线程函数签名,符合pthread要求的void*返回类型 void* write_ticket(void* arg){ sem_wait(&sem); ticketnumber = 5000; sleep(1); printf("%d\n", ticketnumber); sem_post(&sem); return NULL; // 必须返回void*类型的值 } // 同样修正线程函数签名 void* ticketIncrease(void* arg){ sem_wait(&sem); ticketnumber += 1; printf("%d\n", ticketnumber); sem_post(&sem); return NULL; } int main(){ // 检查信号量初始化是否成功,增强健壮性 if (sem_init(&sem, 0, 1) != 0) { perror("sem_init failed"); exit(EXIT_FAILURE); } pthread_t thread1; pthread_t thread2; // 签名匹配后无需强制类型转换 pthread_create(&thread1, NULL, write_ticket, NULL); pthread_create(&thread2, NULL, ticketIncrease, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); // 程序结束前销毁信号量 sem_destroy(&sem); return 0; }
修正说明
- 调整线程函数的返回类型为
void*,并返回NULL,彻底解决类型不匹配导致的未定义行为,让信号量的互斥保护正常工作。 - 给
ticketnumber加上volatile修饰,确保每次读写都直接操作内存,避免编译器缓存导致的变量可见性问题。 - 增加了信号量初始化的错误检查和销毁操作,让代码更规范健壮。
为什么替换sleep为打印就正常?
当把sleep换成打印语句时,线程函数的执行时间极短,可能在栈帧被破坏前就完成了信号量释放和打印操作,侥幸得到了预期结果,但这只是巧合,不是正确的解决方式。
内容的提问来源于stack exchange,提问作者ShreyankGopal
相关产品推荐
相关产品推荐

