1、前言
1.1 定义
POSIX信号量是一种用于同步进程之间对共享资源访问的机制。它允许进程在访问共享资源之前进行互斥和同步操作,以确保数据的一致性和正确性。POSIX信号量通常由一个整数值表示,可以进行原子增减操作,以及等待和通知操作。
1.2 应用场景
- 进程同步:当多个进程需要访问共享资源时,可以使用信号量来确保只有一个进程能够访问资源,从而避免数据竞争和冲突。
- 控制资源访问:信号量可以用于限制对资源的访问数量,例如控制同时访问某个共享资源的进程数量。
- 进程间通信:信号量也可以用于实现进程间的通信和同步,以确保进程之间的协作和顺序执行。
1.3 优缺点
1.3.1 优点
- 灵活性:信号量可以用于实现不同类型的同步和通信需求,包括互斥访问、资源控制和进程同步等。
- 多进程支持:信号量适用于多个进程之间的通信和同步,可以在不同进程之间进行共享和使用。
- 高效性:信号量的实现通常是基于硬件原子操作的,因此在性能上可以比较高效。
1.3.2 缺点
- 复杂性:使用信号量可能需要处理死锁、竞争条件等复杂问题,需要谨慎设计和管理。
- 缺乏语义:信号量本身只是一个整数值,缺乏高层次的语义,需要程序员自行设计合适的同步和通信规则。
- 跨平台兼容性:不同操作系统对于信号量的实现和语义可能存在差异,因此在跨平台开发时需要考虑兼容性问题。
1.4 重要概念
有名信号量(Named Semaphore):
- 有名信号量是一种由操作系统内核维护的具有全局唯一名字的信号量。
- 有名信号量可以在不同进程之间进行共享,因为它们可以通过名称在系统中进行识别和访问。
- 有名信号量通常用于进程间通信和同步,可以在不同进程之间进行共享和使用。
无名信号量(Unnamed Semaphore):
- 无名信号量是一种不具有全局唯一名字的信号量,通常只能在相关的进程或线程之间进行共享。
- 无名信号量通常用于线程间通信和同步,因为它们只能在共享同一内存空间的线程之间进行使用。
- 无名信号量通常使用
sem_init
函数进行初始化,使用sem_destroy
函数进行销毁。
2、编程常用接口
2.1 sem_open 函数
创建或打开有名信号量
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
参数:
- name:信号量的名称
- oflag:标志位,用于指定信号量的行为和权限
- mode:权限模式
- value:信号量的初始值
返回值:
- 成功:返回指向信号量的指针
- 失败:返回 SEM_FAILED,并设置 errno
其中入参的mode选择如下:
S_IRUSR
:用户读权限S_IWUSR
:用户写权限S_IRGRP
:组读权限S_IWGRP
:组写权限S_IROTH
:其他用户读权限S_IWOTH
:其他用户写权限
2.2 sem_close 函数
关闭有名信号量
int sem_close(sem_t *sem);
参数:
- sem:指向要关闭的信号量的指针
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.3 sem_init 函数
初始化无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
- sem:指向要初始化的信号量的指针
- pshared:指定信号量的进程共享性质,非零值表示信号量在进程间共享,零值表示信号量在线程间共享
- value:信号量的初始值
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.4 sem_destroy 函数
销毁无名信号量
int sem_destroy(sem_t *sem);
参数:
- sem:指向要销毁的信号量的指针
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.5 sem_unlink 函数
从系统中删除有名信号量
int sem_unlink(const char *name);
参数:
- name:信号量的名称
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.6 sem_wait 函数
等待信号量减小,如果信号量的值大于0,则将其减小1并立即返回,否则会阻塞当前线程直到信号量变为大于0为止
int sem_wait(sem_t *sem);
参数:
- sem:指向要等待的信号量的指针
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.7 sem_trywait 函数
尝试等待信号量减小的非阻塞版本。如果信号量的值大于0,则将其减小1并立即返回,否则会立即返回,并且不会阻塞当前线程
int sem_trywait(sem_t *sem);
参数:
- sem:指向要尝试等待的信号量的指针
返回值:
- 成功:返回 0
- 失败:若信号量当前不能立即获得,则返回 -1,并设置 errno 为 EAGAIN
2.8 sem_post 函数
增加信号量,信号量值+1
int sem_post(sem_t *sem);
参数:
- sem:指向要增加的信号量的指针
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.9 sem_timedwait 函数
函数允许设置一个超时时间,如果在指定的时间内未能获得信号量,函数将返回一个特定的错误码。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参数:
- sem:指向要等待的信号量的指针
- abs_timeout:绝对时间,指定等待的超时时间
返回值:
-
成功:返回 0
-
失败:若超时或出错,则返回 -1,并设置 errno
struct timespec {
time_t tv_sec; // 秒
long tv_nsec; // 纳秒
};
2.10 sem_getvalue函数
获取当前信号量的值
int sem_getvalue(sem_t *sem, int *sval);
参数:
- sem:指向要获取值的信号量的指针。
- sval:一个整数指针,用于存储信号量的当前值。
返回值:
- 成功:返回 0
- 失败:若超时或出错,则返回 -1,并设置 errno
2.11 接口适用总结
函数 | 适用 |
---|---|
sem_open |
有名信号量 |
sem_close |
有名信号量 |
sem_init |
无名信号量 |
sem_destroy |
无名信号量 |
sem_unlink |
有名信号量 |
sem_wait |
两者皆可 |
sem_trywait |
两者皆可 |
sem_post |
两者皆可 |
sem_timedwait |
两者皆可 |
sem_getvalue |
两者皆可 |
3、编程测试
3.1 有名信号量编程测试
测试代码如下:
#include <semaphore.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 打印时分秒的宏
#define PRINT_MIN_SEC do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec);\
} while (0);printf
// 打印当前信号量的值
void printfValue(sem_t *sem)
{
// 打印当前值
int value;
sem_getvalue(sem, &value);
PRINT_MIN_SEC("Current value of semaphore: %d\n", value);
}
int main(int argc, char *argv[])
{
sem_t *sem;
// 命令行参数
// 第一个参数 I表示初始化 W表示等待 P表示增加信号量的值
if (argc != 2)
{
printf("Usage: %s I|W|P", argv[0]);
return 0;
}
if (!strcmp(argv[1], "I"))
{
PRINT_MIN_SEC("Init sem ...\n");
if((sem = sem_open("sem_p", O_CREAT, 0644, 0)) == SEM_FAILED)
{
perror("sem_open err");
return 0;
}
PRINT_MIN_SEC("Init sem OK\n");
}
else if (!strcmp(argv[1], "W"))
{
if((sem = sem_open("sem_p", 0)) == SEM_FAILED)
{
perror("sem_open err");
return 0;
}
PRINT_MIN_SEC("Wait sem ...\n");
sem_wait(sem);
PRINT_MIN_SEC("Wait sem OK\n");
}
else if (!strcmp(argv[1], "P"))
{
if((sem = sem_open("sem_p", 0)) == SEM_FAILED)
{
perror("sem_open err");
return 0;
}
PRINT_MIN_SEC("Post sem ...\n");
sem_post(sem);
PRINT_MIN_SEC("Post sem OK\n");
}
else
{
printf("Usage: %s I|W|P", argv[0]);
return 0;
}
printfValue(sem);
sem_close(sem);
return 0;
}
通过命令行不同参数实现不同功能I表示初始化 W表示等待 P表示增加信号量的值,编译的时候需要加-pthread,开启一个控制台,执行初始化和等待:
另起一个控制台,发起新的进程执行post操作,阻塞的进程可以成功执行完毕,完成进程间通信:
在/dev/shm目录下可以查看到信号量对应的文件:
3.2 无名信号量编程测试
测试代码如下:
#include <semaphore.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 打印时分秒的宏
#define PRINT_MIN_SEC do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec);\
} while (0);printf
sem_t mutex;
void* thread_function_1(void* arg)
{
PRINT_MIN_SEC("In thread_function_1\n");
while(1)
{
sleep(3);
sem_post(&mutex);
}
}
void* thread_function_2(void* arg)
{
PRINT_MIN_SEC("In thread_function_2\n");
while(1)
{
PRINT_MIN_SEC("thread_function_2 sem_wait ...\n");
sem_wait(&mutex);
PRINT_MIN_SEC("thread_function_2 sem_wait OK\n");
}
}
int main(int argc, char *argv[])
{
sem_init(&mutex, 0, 0);
pthread_t thread;
pthread_create(&thread, NULL, thread_function_1, NULL);
pthread_create(&thread, NULL, thread_function_2, NULL);
pthread_join(thread, NULL);
sem_destroy(&mutex);
return 0;
}
测试无名信号线程间通信,发起两个线程,线程1每隔3秒执行一次sem_post,线程2一直等待获取,测试结果如下:
4、总结
本文阐述了进程间通信之信号量(POSIX)的定义、应用场景、优缺点等,列举了编程中使用的接口,编写了测试用例测试相关功能。