目录
一、信号量定义
二、信号量种类
三、信号量操作
3.1 声明信号量
3.2 初始化信号量
3.3 等待信号量P
3.4 释放信号量V
3.5 销毁信号量
四、代码实现
五、笔试题
一、信号量定义
在线程同步中,信号量是一种同步原语,用于控制多线程对共享资源的访问。信号量通常用于解决生产者-消费者问题、避免资源竞争等多线程编程中的同步与互斥问题。
信号量可以分为两种类型:二进制信号量和计数信号量。
- 二进制信号量(Binary Semaphore):也称为互斥信号量,只有两个值,通常是0和1,用于实现线程之间的互斥访问共享资源,保证在同一时刻只有一个线程可以访问共享资源。
- 计数信号量(Counting Semaphore):可以有多个取值,通常用于控制多个线程对共享资源的访问,可以指定一个初始计数值,每次线程进入临界区时计数值减少,离开时计数值增加。
二、信号量种类
在线程同步中,信号量根据其功能和特性可以分为以下几种种类:
-
互斥信号量(Mutex):也称为二进制信号量,只有两个取值,通常是0和1。用于实现线程之间的互斥访问共享资源,保证在同一时刻只有一个线程可以访问共享资源。常用于解决临界区问题。
-
读写信号量(Read-Write Semaphore):一种特殊的信号量,用于解决读者-写者问题。允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。通过读写信号量可以实现读者之间的并发访问,同时保证写者和读者之间的互斥访问。
-
计数信号量(Counting Semaphore):可以有多个取值,用于控制多个线程对共享资源的访问。可以指定一个初始计数值,每次线程进入临界区时计数值减少,离开时计数值增加。适用于控制资源的并发访问数量。
-
有界缓冲区信号量(Bounded Buffer Semaphore):用于实现生产者-消费者问题。控制生产者向缓冲区中放入数据和消费者从缓冲区中取出数据的同步操作。通过有界缓冲区信号量可以避免生产者过度生产或消费者过度消费的问题。
这些不同类型的信号量在多线程编程中发挥着重要作用,可以帮助开发人员管理线程之间的同步和互斥关系,确保线程安全和避免竞争条件的发生。
三、信号量操作
3.1 声明信号量
sem_t sem;
声明了一个名为 sem 的信号量
3.2 初始化信号量
sem_init(&sem,0,1)
sem_t *sem
:指向信号量变量的指针。
int pshared
:如果设置为0,信号量只能在同一个进程内共享;如果设置为1,信号量可以在多个进程间共享。
unsigned int value
:信号量的初始值。
3.3 等待信号量P
sem_wait(&sem)
sem_wait
函数用于等待信号量。如果信号量的值大于0,它将信号量的值减1,然后继续执行。
如果信号量的值为0,调用线程将被阻塞,直到信号量的值大于0。
3.4 释放信号量V
sem_post(&sem)
sem_post
函数用于释放信号量。它将信号量的值加1。如果有线程因为等待这个信号量而被阻塞,
sem_post
将唤醒其中一个线程。
3.5 销毁信号量
sem_destroy(&sem)
sem_destroy
函数用于销毁信号量,释放与之关联的资源。在程序结束前,销毁所有创建的信号量。
四、代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include <semaphore.h>sem_t sem;int g_val=1;void* fun(void* arg)
{for(int i=0;i<1000;i++){//psem_wait(&sem);//可能阻塞printf("g_val=%d\n",g_val++);sem_post(&sem);//v}
}
int main()
{pthread_t id[5];sem_init(&sem,0,1);int i=0;for(;i<5;i++){pthread_create(&id[i],NULL,fun,NULL);}for(i=0;i<5;i++){pthread_join(id[i],NULL);}sem_destroy(&sem);exit(0);
}
运行结果:
一直会运行到设置的5000次,然后才结束。
五、笔试题
利用多线程和信号量实现了循环打印 "ABC" 序列的功能。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include <semaphore.h>// 定义三个信号量,用于线程间的同步
sem_t sema;
sem_t semb;
sem_t semc;// 线程函数 funa,负责打印字符 'A'
void* funa(void* arg)
{// 循环 5 次,每次打印一个 'A'for(int i = 0; i < 5; i++){// 对信号量 sema 执行 P 操作(等待)// 如果 sema 的值大于 0,则将其减 1 并继续执行// 如果 sema 的值为 0,则线程阻塞,直到 sema 的值大于 0sem_wait(&sema); // 打印字符 'A'printf("A");// 刷新标准输出缓冲区,确保字符 'A' 立即输出fflush(stdout);// 对信号量 semb 执行 V 操作(释放)// 将 semb 的值加 1,唤醒可能因等待 semb 而阻塞的线程sem_post(&semb); }// 线程执行完毕,返回 NULLreturn NULL;
}// 线程函数 funb,负责打印字符 'B'
void* funb(void* arg)
{// 循环 5 次,每次打印一个 'B'for(int i = 0; i < 5; i++){// 对信号量 semb 执行 P 操作(等待)sem_wait(&semb); // 打印字符 'B'printf("B");// 刷新标准输出缓冲区fflush(stdout);// 对信号量 semc 执行 V 操作(释放)sem_post(&semc); }// 线程执行完毕,返回 NULLreturn NULL;
}// 线程函数 func,负责打印字符 'C'
void* func(void* arg)
{// 循环 5 次,每次打印一个 'C'for(int i = 0; i < 5; i++){// 对信号量 semc 执行 P 操作(等待)sem_wait(&semc); // 打印字符 'C'printf("C");// 刷新标准输出缓冲区fflush(stdout);// 对信号量 sema 执行 V 操作(释放)sem_post(&sema); }// 线程执行完毕,返回 NULLreturn NULL;
}int main()
{// 初始化信号量 sema,第二个参数 0 表示该信号量用于线程间同步// 第三个参数 1 表示信号量的初始值为 1sem_init(&sema, 0, 1); // 初始化信号量 semb,初始值为 0sem_init(&semb, 0, 0); // 初始化信号量 semc,初始值为 0sem_init(&semc, 0, 0); // 定义三个线程标识符,用于存储创建的线程的 IDpthread_t id1, id2, id3;// 创建第一个线程,执行 funa 函数pthread_create(&id1, NULL, funa, NULL);// 创建第二个线程,执行 funb 函数pthread_create(&id2, NULL, funb, NULL);// 创建第三个线程,执行 func 函数pthread_create(&id3, NULL, func, NULL);// 等待第一个线程执行完毕pthread_join(id1, NULL);// 等待第二个线程执行完毕pthread_join(id2, NULL);// 等待第三个线程执行完毕pthread_join(id3, NULL);// 销毁信号量 sema,释放相关资源sem_destroy(&sema);// 销毁信号量 sembsem_destroy(&semb);// 销毁信号量 semcsem_destroy(&semc);// 程序正常退出,返回状态码 0exit(0);
}
运行结果: