Linux IPC:POSIX 信号量

POSIX 信号量(Semaphores)是一种同步机制,用于控制对共享资源的访问。POSIX 信号量有两种类型:二进制信号量(Binary Semaphores)和计数信号量(Counting Semaphores)。它们提供了原子的信号量操作,可以用于实现互斥和同步。下面详细介绍 POSIX 信号量的概念、用途、API 以及示例代码。

概述

POSIX 信号量可以分为两类:

  1. 有名信号量 (Named Semaphores):可以在多个进程之间共享。
  2. 无名信号量 (Unnamed Semaphores):仅限于在同一进程内的线程之间共享。

特点

  • 原子操作:信号量操作是原子的,可以防止并发访问导致的问题。
  • 互斥:二进制信号量可以用于互斥访问共享资源。
  • 同步:计数信号量可以用于线程之间的同步。
  • 跨进程:有名信号量可以在不同的进程之间进行同步。

API

POSIX 信号量的主要 API 如下:

有名信号量

  • sem_open():

    • sem_t *sem_open(const char *name, int oflag[, ...]): 创建或打开一个有名信号量。
    • 参数name是信号量的名字,oflag指定创建方式,如O_CREAT表示创建新的信号量,后面可以跟value来设置初始值。
  • sem_close():

    • int sem_close(sem_t *sem): 关闭一个有名信号量。
    • 参数sem是指向信号量的指针。
  • sem_unlink():

    • int sem_unlink(const char *name): 删除一个有名信号量。
    • 参数name是信号量的名字。

无名信号量

  • sem_init():

    • int sem_init(sem_t *sem, int pshared, unsigned int value): 初始化一个无名信号量。
    • 参数sem是指向信号量的指针,pshared指定信号量是否可以在不同线程间共享(非零值表示可以),value是信号量的初始值。
  • sem_destroy():

    • int sem_destroy(sem_t *sem): 销毁一个无名信号量。
    • 参数sem是指向信号量的指针。

共同操作

  • sem_wait():

    • int sem_wait(sem_t *sem): 等待信号量。
    • 参数sem是指向信号量的指针。
  • sem_trywait():

    • int sem_trywait(sem_t *sem): 尝试等待信号量,如果信号量不可用则立即返回。
    • 参数sem是指向信号量的指针。
  • sem_post():

    • int sem_post(sem_t *sem): 释放信号量。
    • 参数sem是指向信号量的指针。
  • sem_getvalue():

    • int sem_getvalue(const sem_t *sem, int *sval): 获取信号量的值。
    • 参数sem是指向信号量的指针,sval是指向整数的指针,用于返回信号量的值。

示例代码

下面是一个简单的示例,展示了如何使用无名信号量来同步多个线程的操作:

1#include <semaphore.h>
2#include <pthread.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6
7// 定义信号量
8sem_t semaphore;
9
10// 线程函数
11void *thread_function(void *arg) {
12    int thread_id = *(int *)arg;
13
14    // 等待信号量
15    sem_wait(&semaphore);
16    printf("Thread %d acquired the semaphore\n", thread_id);
17
18    // 模拟一些操作
19    sleep(1);
20
21    // 释放信号量
22    sem_post(&semaphore);
23    printf("Thread %d released the semaphore\n", thread_id);
24
25    return NULL;
26}
27
28int main() {
29    int i;
30    pthread_t threads[5];
31
32    // 初始化信号量
33    if (sem_init(&semaphore, 0, 1) == -1) {
34        perror("sem_init");
35        exit(EXIT_FAILURE);
36    }
37
38    // 创建线程
39    for (i = 0; i < 5; i++) {
40        int *thread_id = (int *)malloc(sizeof(int));
41        *thread_id = i + 1;
42        if (pthread_create(&threads[i], NULL, thread_function, (void *)thread_id) != 0) {
43            perror("pthread_create");
44            exit(EXIT_FAILURE);
45        }
46    }
47
48    // 等待线程结束
49    for (i = 0; i < 5; i++) {
50        if (pthread_join(threads[i], NULL) != 0) {
51            perror("pthread_join");
52            exit(EXIT_FAILURE);
53        }
54    }
55
56    // 销毁信号量
57    if (sem_destroy(&semaphore) == -1) {
58        perror("sem_destroy");
59        exit(EXIT_FAILURE);
60    }
61
62    return 0;
63}

注意事项

  • 初始化和销毁:确保在使用信号量之前正确初始化它,并在不再需要时销毁它。
  • 死锁避免:避免在锁定多个信号量时发生死锁。一个常见的做法是始终以相同的顺序锁定信号量。
  • 超时等待:POSIX 信号量没有直接提供超时等待的功能,但可以通过结合使用sem_trywait()usleep()等函数实现类似的效果。
  • 线程取消:在使用信号量时,确保处理线程取消的情况,避免资源泄漏。

POSIX 信号量是多线程编程中非常重要的工具,它们可以帮助我们有效地解决并发问题。理解和熟练掌握这些概念对于编写高效、可靠的多线程应用程序至关重要。