【Linux开发】03Linux 线程同步:信号量(Semaphore)
一、问题互斥量只能“锁”不能“排队”前面我们学习了互斥量它可以解决多个线程同时访问共享资源的问题保证同一时间只有一个线程进入临界区。但互斥量只能做到“互斥”无法控制线程的执行顺序。1.1 需要控制顺序的场景假设有两个线程线程 A读取用户输入的数字存入全局变量num。线程 B将num累加到总和sum中。我们希望A 先输入 → B 再累加 → A 再输入 → B 再累加如此交替 5 次。如果用互斥量只能保证 A 和 B 不会同时操作num但不能保证 A 先执行还是 B 先执行。可能出现 B 试图累加时A 还没输入num还是旧值导致错误。1.2 需要一种能“同步顺序”的机制我们需要一种工具不仅能互斥访问还能让线程按指定的顺序执行。比如初始时只允许 A 运行B 等待。A 完成输入后通知 B 可以运行A 自己等待。B 累加后通知 A 可以继续输入B 等待。这种机制就是信号量Semaphore。二、什么是信号量2.1 概念停车场的计数器想象一个停车场门口有一个电子牌显示剩余车位数量每进入一辆车剩余车位减 1P 操作。每离开一辆车剩余车位加 1V 操作。如果剩余车位为 0后面来的车必须等待。信号量就是一个整数计数器它支持两种原子操作P等待如果计数器 0则减 1 并继续否则阻塞等待。V发信号计数器加 1并唤醒一个等待的线程如果有。2.2 二进制信号量与计数信号量二进制信号量值只能是 0 或 1相当于互斥量但功能更强可用于顺序控制。计数信号量值可以大于 1用于控制同时访问资源的线程数量如连接池。2.3 与互斥量的区别特性互斥量信号量作用互斥访问互斥 顺序控制值范围0 或 10 ~ n谁可以解锁只能加锁的线程解锁任何线程都可以 V 操作典型用途保护临界区生产者-消费者、顺序同步三、信号量相关函数信号量 API 与互斥量类似但属于 POSIX 标准头文件semaphore.h。3.1 初始化sem_init#includesemaphore.hintsem_init(sem_t*sem,intpshared,unsignedintvalue);sem指向信号量变量的指针。pshared0 表示线程间使用同一进程非 0 表示进程间使用需共享内存。value信号量的初始值。返回值成功返回 0失败返回 -1。3.2 等待P 操作sem_waitintsem_wait(sem_t*sem);如果信号量值 0将其减 1 并立即返回。如果信号量值 0线程阻塞直到值变为正数被其他线程sem_post。3.3 发信号V 操作sem_postintsem_post(sem_t*sem);将信号量值加 1。如果有线程正在等待该信号量则唤醒其中一个。3.4 销毁sem_destroyintsem_destroy(sem_t*sem);销毁信号量释放资源。四、完整示例控制线程执行顺序4.1 需求线程 A循环 5 次每次从键盘输入一个数字存入num。线程 B循环 5 次每次将num累加到sum。必须严格按照A 输入 → B 累加 → A 输入 → B 累加的顺序执行。4.2 设计思路使用两个信号量sem_one初始值为 0控制线程 B 是否可以累加。B 执行前sem_wait(sem_one)初始为 0所以 B 阻塞A 输入后sem_post(sem_one)值变为 1B 被唤醒。sem_two初始值为 1控制线程 A 是否可以输入。A 执行前sem_wait(sem_two)初始为 1A 可以立即执行输入后sem_post(sem_one)A 再次循环时会sem_wait(sem_two)等待 B 完成累加并sem_post(sem_two)。流程A 先执行sem_wait(sem_two)初始 1 → 0输入数字。A 执行sem_post(sem_one)0 → 1唤醒 B。A 回到循环开始执行sem_wait(sem_two)此时值为 0A 阻塞。B 执行sem_wait(sem_one)1 → 0累加。B 执行sem_post(sem_two)0 → 1唤醒 A。重复 5 次。4.3 完整代码#includestdio.h#includepthread.h#includesemaphore.hstaticsem_tsem_one;// 控制 B 是否可以累加staticsem_tsem_two;// 控制 A 是否可以输入staticintnum;// 共享变量void*read(void*arg){for(inti0;i5;i){// 等待许可初始 sem_two 为 1所以第一次直接通过sem_wait(sem_two);printf(Input num: );scanf(%d,num);// 通知 B 可以累加sem_post(sem_one);}returnNULL;}void*accu(void*arg){intsum0;for(inti0;i5;i){// 等待 A 输入sem_wait(sem_one);sumnum;// 通知 A 可以输入下一个数字sem_post(sem_two);}printf(Result: %d\n,sum);returnNULL;}intmain(){pthread_tt1,t2;// 初始化信号量sem_init(sem_one,0,0);// 初始为 0B 一开始会阻塞sem_init(sem_two,0,1);// 初始为 1A 可以立即执行pthread_create(t1,NULL,read,NULL);pthread_create(t2,NULL,accu,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);sem_destroy(sem_one);sem_destroy(sem_two);return0;}4.4 运行结果Input num: 10 Input num: 20 Input num: 30 Input num: 40 Input num: 50 Result: 150每次输入一个数程序就会累加不会出现 B 抢在 A 前面运行的情况。五、信号量与互斥量的对比场景使用互斥量使用信号量保护共享数据✅ 适合✅ 也可二进制信号量控制执行顺序❌ 无法实现✅ 非常适合允许多个线程同时访问如连接池❌ 只能一个✅ 计数信号量解锁者限制必须加锁者解锁任何线程都可以 V 操作六、常见问题与注意事项6.1 忘记初始化信号量sem_tsem;// 忘记 sem_init直接使用会导致未定义行为。6.2 信号量值溢出sem_post可能使信号量值超过初始设定的最大值通常是SEM_VALUE_MAX但一般不会手动设置极大值。6.3 死锁如果两个线程互相等待对方sem_post且初始值设置不当可能造成死锁。例如两个信号量初始都为 0两个线程都先sem_wait对方就会死锁。6.4 进程间信号量如果pshared非 0信号量可用于不同进程需放在共享内存中。初学者先掌握线程间使用pshared0。6.5 编译链接需要链接pthread库同时也要链接rt某些系统需要-lrt但通常-pthread已包含gcc-pthreadsem_example.c-osem_example七、总结概念说明信号量一个整数计数器支持原子增减和阻塞等待P 操作wait值减 1若值为 0 则阻塞V 操作post值加 1唤醒等待线程二进制信号量值 0/1类似互斥量计数信号量值 1控制并发数量顺序控制通过两个信号量可以实现线程交替执行信号量使用四步曲1. sem_init(sem, 0, 初始值) // 初始化 2. sem_wait(sem) // 等待P 3. sem_post(sem) // 发信号V 4. sem_destroy(sem) // 销毁
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2496391.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!