1.基本概念
信号量的概念:
信号量(semaphore)本质上是一个计数器(一个大于等于0的整数值),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。 在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)。
信号量的结构:
信号量的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。 信号量的值与相应资源的使用情况有关: 当信号量的值大于0时,表示当前可用资源的数量 ;当它的值小于0时,其绝对值表示等待使用该资源的进程个数 。
三,信号量对应的操作:
信号量的值仅能由PV操作来改变。
什么是PV操作:
P操作:
sem变量减1 (获得资源)
若sem>=0,则P操作返回,该线程程可以“通过”并继续执行。
若sem<0,则该线程被阻塞,进入操作系统的阻塞队列。
V操作:
sem变量加1(释放资源)
若sem>0,则V操作返回,该线程继续执行。
若sem<=0,则从阻塞队列中唤醒一个阻塞在该信号量上的线程,然后再返回原线程(调用ν操作的线程)继续执行。
Linux多进程访问共享资源时,需要按下列步骤进行操作:
(1)检测控制这个资源的信号量的值(看是否大于0)。
(2)如果信号量是正数,就可以使用这个资源。进程将信号量的值“减 1”,表示当前进程占用了一份资源。访问资源结束后再执行“加 1”操作。
(3)如果信号量是0,那么进程进入睡眠状态,直到信号量的值重新大于0时被唤醒,转入第一步操作。
四,信号量的分类:
信号量按照使用场景分为 :二值信号量和计数信号量:
二值信号量:指初始值为 1 的信号量,此类信号量只有 1 和 0 两个值,通常用来代替锁机制实现线程同步, 在一个时刻仅允许有一个资源持有者;
计数信号量:指初始值大于 1 的信号量,当进程中存在多个线程,但某公共资源允许同时访问的线程数量是有限的,它允许在一个时刻至多有count个资源持有者,这时就可以用计数信号量来限制同时访问资源的线程数量。
临界资源在同一时刻只允许一个进程使用,此时的信号量是一个二值信号量,它只控制一个资源;另一种应用于处理多个共享资源(例如多台打印机的分配),信号量在其中起到记录空闲资源数目的作用,此时的信号量是计数信号量。
2.未命名信号量 (POSIX)
pshared = 0 : 信号量将在调用进程中的线程间进行分享
pshared != 0 : 信号量将在进程间共享
初始化信号量sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数说明:
a. sem为指向未初始化信号量结构的一个指针
b. pshared参数表示这个信号量是在进程的线程之间共享,还是在进程之间共享。
如果pshared的值为0,那么这个信号量会在进程的线程之间共享,并且应该位于对所有线程都可见的某个地址
如果pshared非零,那么这个信号量将在进程之间共享,并且应该位于共享内存的某个区域,(因为fork创建的子进程会继承父进程的内存映射,所以它也可以获取信号量)。
任何可以访问共享内存区域的进程都可以使用sem_post、sem_wait等对这个信号量进行操作。
c. value指定信号量的初始值
功能:
初始化信号量
返回值:成功返回0,失败返回-1
##
操作未命名信号量所使用的函数与操作命名信号量使用的函数是一样的( sem_wait() sem_post() 以及 sem_getvalue() 等)。 此外,还需要用到另外两个函数。
sem_init() 函数对一个信号量进行初始化并通知系统该信号量会在进程间共享还是在单个进程中的 线程间共享。
sem_destroy(sem) 函数销毁一个信号量。这些函数不应该被应用到命名信号量上。
第二个参数
pshared = 0 , 线程的进程之间
pshared > 0 , 进程之间使用,该信号量放在一个共享的存储空间---》 shmopen(3), mmap(2), shmget(2)
父子进程之间
sem.c
#include <stdio.h>
#include <semaphore.h> // 头文件
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
int main(void)
{
sem_t *sem_id = NULL; // 信号量指针(空指针) 可读写
sem_id = mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); // 创建公共虚拟空间,父子进程共同使用
sem_init(sem_id, 1, 1); // 第二个参数pshared > 0,进程之间共同使用
// 创建父子进程 // pshared = 0 , 进程的线程之间进行通信
pid_t pid;
pid = fork();
// 父进程
if(pid > 0)
{
while(1)
{
sem_wait(sem_id); // 是否大于0,= 0 阻塞,直到 >0 ,继续运行并-1
printf("this is parent process.\n");
sleep(1);
}
}
// 子进程
else if(pid == 0)
{
while(1)
{
printf("this is child process.\n");
sleep(5);
sem_post(sem_id); // sem_id 信号量++,父进程又可以运行了,子进程控制父进程
}
}
return 0;
}
//gcc sem.c -pthread
3.命名信号量(POSIX)
1. sem_open()函数打开或创建一个(命名信号量)信号量并返回一个句柄以供后续调用使用,如果这个调用会创建信号量的话还会对所创建的信号量进行初始化。
2. sem_post(sem)和sem_wait(sem)函数分别递增和递减一个信号量值。
3. sem_getvalue()函数将sem引用的信号量的当前值通过sval指向的int变量返回。
4. sem_close()函数删除调用进程与它之前打开的一个信号量之间的关联关系。
5. sem_unlink()函数删除一个信号量名字并将其标记为在所有进程关闭该信号量时删除该信号量。
无亲缘关系进程进行通讯-----命名信号量
named_sem_1.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(void)
{
sem_t *sem; // 返回地址指针
int i = 0; // 信号量字符串 使用 O_CREAT创建信号量 要+ 权限 + 初始值
sem = sem_open("NAMED_SEM", O_CREAT, 0666, 0); // 命名信号量
while(1)
{
sem_wait(sem); // sem = 0 阻塞直到 sem > 0, sem > 0 则 sem - 1,返回
printf("process 1: i = %d\n", i++);
}
sem_close(sem); // 关闭信号量所有关系
return 0;
}
named_sem_2.c
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(void)
{
sem_t *sem;
int i = 0;
sem = sem_open("NAMED_SEM", O_CREAT, 0666, 0); // 命名信号量
while(1)
{
printf("process 2: i = %d\n", i++);
sleep(1);
sem_post(sem); // 发布信号量(信号量+1), 加1后第一个程序可以运行
}
sem_close(sem); // 关闭信号量所有关系,只是解除关系。删除要使用sem_unlink
return 0;
}
3.线程间信号量同步
sem_thread.c 使用线程2控制线程1
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
void *thread1_function(void *arg); //函数声明
void *thread2_function(void *arg);
int count = 0;
sem_t sem; // 信号量,线程共用主进程信号量,所以不需要特别创建共享内存
int main(void)
{
pthread_t pthread1, pthread2;
int ret;
sem_init(&sem, 0, 0); // 线程间信号量 ---》 第二个参数 = 0,初始数值为0
ret = pthread_create(&pthread1, NULL, thread1_function, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
ret = pthread_create(&pthread2, NULL, thread2_function, NULL);
if(ret != 0)
{
perror("pthread_craet");
exit(1);
}
pthread_join(pthread1, NULL); //监测线程1结束
pthread_join(pthread2, NULL);
printf("the thred is over ,process is over too.\n");
return 0;
}
//线程所有资源依赖于进程,进程结束线程也会结束
//线程1
void *thread1_function(void *arg) // arg参数(指针类型)
{
while(1)
{
sem_wait(&sem); // 等待信号量
printf("thread1 count = %d\n", count++);
sleep(1);
}
return NULL;
}
//线程2
void *thread2_function(void *arg)
{
while(1)
{
printf("thread2 is running !\n");
sleep(5);
sem_post(&sem); // 发布信号量, 使用线程2控制线程1
}
return NULL;
}
编译: gcc sem_thread.c -pthread
结果:
mmap创建一个内存映射