一、概述
信号量(Semaphore)是操作系统中的一种同步机制,用于控制多个线程或进程对共享资源的访问。在C语言中,信号量可以通过使用POSIX信号量库或System V信号量库来实现。
信号量本质上是一个计数器,用于表示可用资源的数量。通过对信号量的操作,可以实现对资源的控制和同步。
在C语言中,使用信号量通常需要以下几个步骤:
- 初始化信号量:在使用信号量之前,需要对其进行初始化,设置初始值和共享类型。
- 等待信号量:线程或进程在执行关键区段代码之前,需要等待信号量。如果信号量的值大于0,则将其减1并立即返回;如果信号量的值为0,则线程或进程将被阻塞,直到信号量的值变为大于0。
- 增加信号量:在执行完关键区段代码后,需要增加信号量的值,以表示资源已经释放。如果有其他线程或进程在等待该信号量,则会将其唤醒。
通过这些步骤,信号量可以在多线程或多进程环境中实现对共享资源的互斥访问和同步操作,避免出现竞争条件和死锁等问题。
二、原子性
C语言中的信号量函数通常是原子操作,但具体取决于操作系统和信号量实现。一般情况下,操作系统提供的信号量函数会确保原子性,以避免竞争条件和数据不一致。这些函数在执行时会使用内核机制来锁定信号量,确保同一时刻只有一个线程能够访问和操作信号量。
然而,需要注意的是,原子性并不总是能够得到保证。在某些情况下,特别是在多处理器系统中,可能会出现一些异常情况,如硬件中断或处理器调度等,可能会破坏原子性。因此,在使用信号量时,需要根据具体的平台和需求来评估原子性的保证程度,并采取适当的同步措施来确保数据的安全性。
三、主要函数
信号量函数主要包括以下几个:
sem_init
:用于初始化未命名的信号量。sem_destroy
:用于销毁由sem_init
函数初始化的信号量。sem_wait
:用于等待信号量,即试图减少信号量的值。如果信号量的当前值大于0,则将其减少1并立即返回。如果信号量的当前值为0,则调用线程将被阻塞,直到信号量的值大于0。sem_trywait
:类似于sem_wait
,但如果信号量的值为0,它不会阻塞调用线程,而是立即返回一个错误。sem_post
:用于增加信号量的值。如果有其他线程因调用sem_wait
而被阻塞,该函数会将其唤醒。
两个进程间使用同一个信号量可以通过使用命名信号量来实现。命名信号量有一个唯一的名称,不同进程可以通过该名称来访问和操作同一个信号量。
四、命名信号量
命名信号量是一种具有唯一标识符的信号量,可以通过该标识符在不同进程之间共享和访问。
未命名信号量是一种在同一进程内部使用的信号量,它没有唯一的标识符,只能通过该进程内的变量进行访问和使用。未命名信号量通常用于同步和互斥操作。
以下是两个进程间使用同一个命名信号量的基本步骤:
-
创建命名信号量:在一个进程中,使用
sem_open
函数创建一个命名信号量,并指定一个唯一的名称。例如:sem_t *semaphore = sem_open("/my_semaphore", O_CREAT, 0644, 1);
sem_open
函数的原型为:
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
参数说明:
-
name
:命名信号量的名称,以斜杠`/`开头; -
oflag
:标志位,用于指定创建或打开信号量的方式。常用的选项有: -
O_CREAT
:如果信号量不存在,则创建一个新的,并返回一个指向该信号量的指针; -
O_EXCL
:与`O_CREAT`一同使用,确保只有当前进程创建该信号量; -
mode
:权限标志,用于设置信号量的访问权限; -
value
:信号量初始值。
-
在另一个进程中打开命名信号量:在另一个进程中,使用
sem_open
函数打开相同的命名信号量,通过提供相同的名称来获取对信号量的访问。例如:sem_t *semaphore = sem_open("/my_semaphore", 0);
sem_open
函数的原型为:
sem_t *sem_open(const char *name, int oflag);
-
使用信号量进行同步:两个进程都可以使用
sem_wait
和sem_post
函数来对信号量进行操作,实现同步和通信。 -
关闭和删除命名信号量:在使用完命名信号量后,每个进程都应该使用
sem_close
函数关闭信号量。最后,可以使用sem_unlink
函数删除命名信号量。
需要注意的是,不同进程需要确保使用相同的命名信号量名称,以便能够访问和操作同一个信号量对象。
五、信号量函数sem_init
其原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数说明:
-
sem
:指向要初始化的信号量的指针。 -
pshared
:指示信号量的类型,可以是以下两种取值: -
0
:表示信号量是进程内的,只能由同一进程内线程间共享。 -
1
:表示信号量是进程间共享的,可以由不同进程间的线程共享。 -
value
:指示信号量的初始值。
函数的返回值是一个整数,表示函数的执行结果。成功初始化信号量后,sem_init()函数返回0;否则,返回-1,并设置错误码errno供程序进行错误处理。
信号量可以用来表示资源的数量,资源的获取和释放都会影响信号量的值。当信号量的值大于0时,表示还有可用的资源;当信号量的值为0时,表示资源已经全部被占用;当信号量的值小于0时,表示当前有等待资源的线程/进程存在。
注意事项:
-
未命名信号量可以在进程间共享,但是要求相关进程有共享的内存区域。
-
命名信号量可以在不同的进程中使用,不需要共享内存区域,但是需要提供一个在系统中唯一的名字。
-
使用命名信号量时需要注意设置正确的权限,因为不同进程可能有不同的用户和权限。
六、示例(未做测试)
程序A:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
int main() {
// 创建并初始化命名信号量
sem_t *semaphore = sem_open("/my_semaphore", O_CREAT, 0644, 1);
if (semaphore == SEM_FAILED) {
perror("sem_open");
exit(EXIT_FAILURE);
}
// 使用信号量进行同步
sem_wait(semaphore); // 等待信号量
printf("Application A is running...\n");
sem_post(semaphore); // 增加信号量
// 关闭命名信号量
sem_close(semaphore);
return 0;
}
程序B:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
int main() {
// 打开命名信号量
sem_t *semaphore = sem_open("/my_semaphore", 0);
if (semaphore == SEM_FAILED) {
perror("sem_open");
exit(EXIT_FAILURE);
}
// 使用信号量进行同步
sem_wait(semaphore); // 等待信号量
printf("Application B is running...\n");
sem_post(semaphore); // 增加信号量
// 关闭命名信号量
sem_close(semaphore);
return 0;
}
程序A创建并初始化了命名信号量/my_semaphore
,然后程序B通过打开同一个命名信号量来进行进程间同步。
参考: