跳转到内容

C 语言信号量

来自代码酷


信号量(Semaphore)是C语言多线程编程中用于控制资源访问的同步机制,由荷兰计算机科学家艾兹赫尔·戴克斯特拉于1965年提出。它通过计数器管理对共享资源的访问权限,解决多线程环境下的竞态条件问题。

基本概念[编辑 | 编辑源代码]

信号量本质上是一个非负整数计数器,支持两种原子操作:

  • P操作(Proberen,测试):请求资源,计数器减1。若计数器为0则阻塞线程。
  • V操作(Verhogen,增加):释放资源,计数器加1并唤醒等待线程。

数学表示为: {P(S):while S0 do skip;S:=S1V(S):S:=S+1

信号量类型[编辑 | 编辑源代码]

二进制信号量[编辑 | 编辑源代码]

计数器取值仅为0或1,相当于互斥锁(Mutex)。

计数信号量[编辑 | 编辑源代码]

计数器可取任意非负整数值,用于控制多实例资源访问。

POSIX信号量实现[编辑 | 编辑源代码]

C语言通过`<semaphore.h>`提供信号量支持:

#include <semaphore.h>

// 初始化无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);

// 销毁信号量
int sem_destroy(sem_t *sem);

// P操作(等待)
int sem_wait(sem_t *sem);    // 阻塞版本
int sem_trywait(sem_t *sem); // 非阻塞版本

// V操作(释放)
int sem_post(sem_t *sem);

代码示例[编辑 | 编辑源代码]

生产者-消费者问题[编辑 | 编辑源代码]

经典案例展示信号量如何解决线程同步问题:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 5

sem_t empty, full, mutex;
int buffer[BUFFER_SIZE];
int in = 0, out = 0;

void *producer(void *arg) {
    for (int i = 0; i < 10; i++) {
        sem_wait(&empty);  // 等待空位
        sem_wait(&mutex);  // 进入临界区
        
        buffer[in] = i;
        printf("Produced: %d\n", buffer[in]);
        in = (in + 1) % BUFFER_SIZE;
        
        sem_post(&mutex); // 离开临界区
        sem_post(&full);   // 增加可用产品计数
    }
    return NULL;
}

void *consumer(void *arg) {
    for (int i = 0; i < 10; i++) {
        sem_wait(&full);  // 等待产品
        sem_wait(&mutex);  // 进入临界区
        
        printf("Consumed: %d\n", buffer[out]);
        out = (out + 1) % BUFFER_SIZE;
        
        sem_post(&mutex); // 离开临界区
        sem_post(&empty); // 增加空位计数
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;
    
    sem_init(&empty, 0, BUFFER_SIZE);
    sem_init(&full, 0, 0);
    sem_init(&mutex, 0, 1);
    
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    
    sem_destroy(&empty);
    sem_destroy(&full);
    sem_destroy(&mutex);
    
    return 0;
}

输出示例

Produced: 0
Consumed: 0
Produced: 1
Consumed: 1
...
Produced: 9
Consumed: 9

工作原理图解[编辑 | 编辑源代码]

sequenceDiagram participant Producer participant Semaphore participant Consumer Producer->>Semaphore: sem_wait(empty) Semaphore-->>Producer: 允许访问 Producer->>Semaphore: sem_wait(mutex) Producer->>Buffer: 写入数据 Producer->>Semaphore: sem_post(mutex) Producer->>Semaphore: sem_post(full) Consumer->>Semaphore: sem_wait(full) Semaphore-->>Consumer: 允许访问 Consumer->>Semaphore: sem_wait(mutex) Consumer->>Buffer: 读取数据 Consumer->>Semaphore: sem_post(mutex) Consumer->>Semaphore: sem_post(empty)

实际应用场景[编辑 | 编辑源代码]

1. 数据库连接池:通过计数信号量控制最大连接数 2. 打印任务队列:二进制信号量管理打印机访问 3. 多线程下载:信号量限制同时下载的线程数量 4. GUI事件处理:防止界面线程与工作线程冲突

常见问题[编辑 | 编辑源代码]

死锁风险[编辑 | 编辑源代码]

错误的使用顺序可能导致死锁:

// 错误示例:反向锁定顺序
thread1: sem_wait(A); sem_wait(B);
thread2: sem_wait(B); sem_wait(A);

优先级反转[编辑 | 编辑源代码]

高优先级线程因等待低优先级线程持有的信号量而被阻塞。解决方案包括:

  • 优先级继承协议
  • 优先级天花板协议

扩展阅读[编辑 | 编辑源代码]

  • 信号量与互斥锁的区别:
特性 信号量 互斥锁
所有者 有(锁定线程)
计数 可大于1 只能是0/1
用途 同步 互斥
  • 其他同步机制:
    • 条件变量(Condition Variables)
    • 读写锁(Read-Write Locks)
    • 屏障(Barriers)

最佳实践[编辑 | 编辑源代码]

1. 始终初始化信号量计数器为正确值 2. 避免信号量的嵌套使用 3. 使用命名信号量跨进程同步时注意清理 4. 考虑使用RAII模式管理信号量资源 5. 在实时系统中优先考虑优先级继承信号量