Linux中信号量的相关操作

Linux中信号量的相关操作

Linux系统中sem_init()函数

函数声明

c 复制代码
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

函数参数详解

1. sem - 信号量指针

  • 作用:指向要初始化的信号量对象
  • 类型sem_t *
  • 注意:必须指向有效的内存地址
c 复制代码
// 正确示例
sem_t my_sem;
sem_init(&my_sem, 0, 1);  // 使用栈上变量的地址

// 或者使用动态分配
sem_t *sem_ptr = malloc(sizeof(sem_t));
sem_init(sem_ptr, 0, 1);

2. pshared - 共享标志

  • 作用:指定信号量的共享范围
  • 取值
    • 0:信号量在线程间共享(同一进程内)
    • 非0:信号量在进程间共享(需要放在共享内存中)
c 复制代码
// 线程间共享
sem_t thread_sem;
sem_init(&thread_sem, 0, 1);  // 线程间共享

// 进程间共享(需要特殊处理)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

sem_t *create_process_semaphore() {
    // 创建共享内存区域
    sem_t *psem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE,
                      MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (psem != MAP_FAILED) {
        sem_init(psem, 1, 1);  // 进程间共享
    }
    return psem;
}

3. value - 初始值

  • 作用:信号量的初始计数值
  • 范围:必须是非负整数
  • 含义
    • 0:初始时无可用资源,所有等待者阻塞
    • N:初始时有N个可用资源
c 复制代码
// 二进制信号量(互斥锁)
sem_t binary_sem;
sem_init(&binary_sem, 0, 1);  // 初始值为1,类似互斥锁

// 计数信号量(资源池)
#define POOL_SIZE 5
sem_t pool_sem;
sem_init(&pool_sem, 0, POOL_SIZE);  // 初始有5个资源可用

函数返回值

  • 成功 :返回 0
  • 失败 :返回 -1,并设置相应的 errno
c 复制代码
#include <errno.h>
#include <stdio.h>

sem_t my_sem;
if (sem_init(&my_sem, 0, 1) == -1) {
    perror("sem_init failed");
    switch(errno) {
        case EINVAL:
            printf("参数无效\n");
            break;
        case ENOSYS:
            printf("系统不支持信号量\n");
            break;
        default:
            printf("未知错误\n");
    }
    exit(EXIT_FAILURE);
}

函数作用

sem_init() 用于初始化未命名的POSIX信号量,主要用于:

  1. 线程同步:协调多个线程的执行顺序
  2. 资源计数:限制对有限资源的并发访问
  3. 互斥保护:实现临界区的互斥访问
c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

#define THREAD_COUNT 3

sem_t counter_sem;
int shared_counter = 0;

void* thread_function(void* arg) {
    int thread_id = *(int*)arg;
    
    sem_wait(&counter_sem);  // 获取信号量
    {
        // 临界区开始
        int temp = shared_counter;
        printf("线程%d读取值: %d\n", thread_id, temp);
        sleep(1);  // 模拟处理时间
        shared_counter = temp + 1;
        printf("线程%d写入值: %d\n", thread_id, shared_counter);
        // 临界区结束
    }
    sem_post(&counter_sem);  // 释放信号量
    
    return NULL;
}

int main() {
    pthread_t threads[THREAD_COUNT];
    int thread_ids[THREAD_COUNT];
    
    // 初始化二进制信号量(互斥信号量)
    sem_init(&counter_sem, 0, 1);
    
    // 创建线程
    for (int i = 0; i < THREAD_COUNT; i++) {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("最终计数器值: %d\n", shared_counter);
    
    sem_destroy(&counter_sem);  // 销毁信号量
    return 0;
}

使用注意事项

1. 内存管理

c 复制代码
// 错误示例:使用未分配的内存
sem_t *bad_sem;
sem_init(bad_sem, 0, 1);  // 段错误!bad_sem指向随机地址

// 正确示例
sem_t good_sem;  // 栈上分配
sem_init(&good_sem, 0, 1);

// 或者堆上分配
sem_t *heap_sem = malloc(sizeof(sem_t));
sem_init(heap_sem, 0, 1);
// 记得最后要free(heap_sem);

2. 进程间共享的特殊要求

c 复制代码
// 进程间共享信号量的正确用法
#include <sys/mman.h>

typedef struct {
    sem_t proc_sem;
    int shared_data;
} shared_memory_t;

shared_memory_t* create_shared_memory() {
    // 创建共享内存区域
    shared_memory_t *shm = mmap(NULL, sizeof(shared_memory_t),
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shm == MAP_FAILED) {
        return NULL;
    }
    
    // 在共享内存中初始化信号量
    if (sem_init(&shm->proc_sem, 1, 1) == -1) {
        munmap(shm, sizeof(shared_memory_t));
        return NULL;
    }
    
    shm->shared_data = 0;
    return shm;
}

3. 避免重复初始化

c 复制代码
sem_t my_sem;

// 错误:重复初始化(未定义行为)
sem_init(&my_sem, 0, 1);
sem_init(&my_sem, 0, 1);  // 错误!

// 正确:一次初始化,多次使用
void init_if_needed(sem_t *sem) {
    static int initialized = 0;
    if (!initialized) {
        sem_init(sem, 0, 1);
        initialized = 1;
    }
}

易错点及解决方案

1. 忘记销毁信号量

c 复制代码
// 错误:忘记调用sem_destroy
void problematic_function() {
    sem_t sem;
    sem_init(&sem, 0, 1);
    // 使用信号量...
    // 忘记调用sem_destroy(&sem);  // 资源泄漏!
}

// 正确:使用RAII模式的思想
typedef struct {
    sem_t sem;
    int initialized;
} safe_sem_t;

void safe_sem_init(safe_sem_t *safe_sem, int pshared, unsigned int value) {
    if (sem_init(&safe_sem->sem, pshared, value) == 0) {
        safe_sem->initialized = 1;
    }
}

void safe_sem_destroy(safe_sem_t *safe_sem) {
    if (safe_sem->initialized) {
        sem_destroy(&safe_sem->sem);
        safe_sem->initialized = 0;
    }
}

2. 错误的初始值设置

c 复制代码
// 错误:初始值设置不当可能导致逻辑错误
sem_t sem;
sem_init(&sem, 0, 0);  // 初始为0,所有等待者立即阻塞

// 如果这样使用可能造成死锁
sem_wait(&sem);  // 立即阻塞,除非其他地方有sem_post

// 正确:根据需求合理设置初始值
#define MAX_CONNECTIONS 10
sem_t connection_pool;
sem_init(&connection_pool, 0, MAX_CONNECTIONS);  // 资源池模式

完整使用示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>

#define BUFFER_SIZE 5
#define PRODUCER_COUNT 2
#define CONSUMER_COUNT 2

typedef struct {
    int buffer[BUFFER_SIZE];
    int in, out;
    sem_t empty;    // 空槽位信号量
    sem_t full;     // 满槽位信号量
    sem_t mutex;    // 互斥信号量
} buffer_t;

buffer_t shared_buffer;

void init_buffer(buffer_t *buf) {
    buf->in = buf->out = 0;
    
    // 初始化信号量
    if (sem_init(&buf->empty, 0, BUFFER_SIZE) == -1) {
        perror("sem_init empty failed");
        exit(EXIT_FAILURE);
    }
    
    if (sem_init(&buf->full, 0, 0) == -1) {
        perror("sem_init full failed");
        sem_destroy(&buf->empty);
        exit(EXIT_FAILURE);
    }
    
    if (sem_init(&buf->mutex, 0, 1) == -1) {
        perror("sem_init mutex failed");
        sem_destroy(&buf->empty);
        sem_destroy(&buf->full);
        exit(EXIT_FAILURE);
    }
}

void destroy_buffer(buffer_t *buf) {
    sem_destroy(&buf->empty);
    sem_destroy(&buf->full);
    sem_destroy(&buf->mutex);
}

void produce_item(buffer_t *buf, int item) {
    sem_wait(&buf->empty);  // 等待空槽位
    sem_wait(&buf->mutex);  // 进入临界区
    
    // 生产项目
    buf->buffer[buf->in] = item;
    buf->in = (buf->in + 1) % BUFFER_SIZE;
    printf("生产: %d\n", item);
    
    sem_post(&buf->mutex);  // 离开临界区
    sem_post(&buf->full);   // 增加满槽位计数
}

int consume_item(buffer_t *buf) {
    sem_wait(&buf->full);   // 等待满槽位
    sem_wait(&buf->mutex);  // 进入临界区
    
    // 消费项目
    int item = buf->buffer[buf->out];
    buf->out = (buf->out + 1) % BUFFER_SIZE;
    printf("消费: %d\n", item);
    
    sem_post(&buf->mutex);  // 离开临界区
    sem_post(&buf->empty);  // 增加空槽位计数
    
    return item;
}

void* producer(void *arg) {
    int id = *(int*)arg;
    for (int i = 0; i < 5; i++) {
        int item = id * 100 + i;
        produce_item(&shared_buffer, item);
        sleep(1);
    }
    return NULL;
}

void* consumer(void *arg) {
    int id = *(int*)arg;
    for (int i = 0; i < 5; i++) {
        consume_item(&shared_buffer);
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t producers[PRODUCER_COUNT];
    pthread_t consumers[CONSUMER_COUNT];
    int producer_ids[PRODUCER_COUNT];
    int consumer_ids[CONSUMER_COUNT];
    
    // 初始化缓冲区
    init_buffer(&shared_buffer);
    
    // 创建生产者线程
    for (int i = 0; i < PRODUCER_COUNT; i++) {
        producer_ids[i] = i;
        pthread_create(&producers[i], NULL, producer, &producer_ids[i]);
    }
    
    // 创建消费者线程
    for (int i = 0; i < CONSUMER_COUNT; i++) {
        consumer_ids[i] = i;
        pthread_create(&consumers[i], NULL, consumer, &consumer_ids[i]);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < PRODUCER_COUNT; i++) {
        pthread_join(producers[i], NULL);
    }
    for (int i = 0; i < CONSUMER_COUNT; i++) {
        pthread_join(consumers[i], NULL);
    }
    
    // 清理资源
    destroy_buffer(&shared_buffer);
    printf("程序执行完毕\n");
    
    return 0;
}

总结

sem_init() 是POSIX信号量编程的基础函数,正确使用需要注意:

  1. 内存管理:确保信号量对象有有效的内存地址
  2. 共享模式:正确设置pshared参数
  3. 初始值:根据应用场景合理设置初始计数值
  4. 资源清理 :使用后务必调用sem_destroy()释放资源
  5. 错误处理:检查返回值并适当处理错误情况

通过合理使用信号量,可以有效解决多线程/多进程环境下的同步和互斥问题。



Linux系统中sem_wait()函数

函数声明

c 复制代码
#include <semaphore.h>

int sem_wait(sem_t *sem);

函数参数详解

sem - 信号量指针

  • 作用:指向已初始化的信号量对象
  • 类型sem_t *
  • 要求:必须指向有效的、已初始化的信号量
c 复制代码
#include <semaphore.h>

// 正确使用示例
sem_t my_sem;

void init_and_use_semaphore() {
    // 1. 先初始化信号量
    if (sem_init(&my_sem, 0, 1) == -1) {
        perror("sem_init failed");
        return;
    }
    
    // 2. 然后才能使用sem_wait
    if (sem_wait(&my_sem) == -1) {
        perror("sem_wait failed");
    } else {
        printf("成功获取信号量\n");
        // 执行临界区代码...
        sem_post(&my_sem);  // 释放信号量
    }
}

函数返回值

  • 成功 :返回 0
  • 失败 :返回 -1,并设置相应的 errno

常见错误码:

  • EINVAL:参数sem不是有效的信号量
  • EINTR:操作被信号中断
c 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>

void safe_sem_wait(sem_t *sem) {
    int result;
    
    do {
        result = sem_wait(sem);
        if (result == -1) {
            if (errno == EINTR) {
                // 被信号中断,继续等待
                printf("sem_wait被信号中断,继续等待...\n");
                continue;
            } else {
                // 其他错误
                perror("sem_wait失败");
                break;
            }
        }
    } while (result == -1 && errno == EINTR);
    
    if (result == 0) {
        printf("成功获取信号量\n");
    }
}

函数作用

sem_wait() 是POSIX信号量的核心操作函数,主要作用:

  1. 原子性减一操作:将信号量的值减1(如果值大于0)
  2. 阻塞等待:如果信号量值为0,则调用线程阻塞直到信号量值变为正数
  3. 同步控制:实现线程/进程间的同步和互斥
c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

sem_t resource_sem;
int shared_resource = 0;

void* worker_thread(void* arg) {
    int thread_id = *(int*)arg;
    
    printf("线程%d: 尝试获取资源...\n", thread_id);
    
    // sem_wait会阻塞直到信号量可用
    sem_wait(&resource_sem);
    
    printf("线程%d: 获得资源,处理中...\n", thread_id);
    
    // 临界区开始
    int temp = shared_resource;
    sleep(1);  // 模拟处理时间
    shared_resource = temp + 1;
    printf("线程%d: 更新共享资源为%d\n", thread_id, shared_resource);
    // 临界区结束
    
    sem_post(&resource_sem);
    printf("线程%d: 释放资源\n", thread_id);
    
    return NULL;
}

int main() {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;
    
    // 初始化二进制信号量(互斥信号量)
    sem_init(&resource_sem, 0, 1);
    
    pthread_create(&t1, NULL, worker_thread, &id1);
    pthread_create(&t2, NULL, worker_thread, &id2);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("最终共享资源值: %d\n", shared_resource);
    sem_destroy(&resource_sem);
    
    return 0;
}

使用注意事项

1. 信号量必须先初始化

c 复制代码
// 错误示例:使用未初始化的信号量
sem_t uninitialized_sem;

void wrong_usage() {
    // 未定义行为!信号量未初始化
    sem_wait(&uninitialized_sem);  // 可能导致段错误或不可预测行为
}

// 正确示例
void correct_usage() {
    sem_t sem;
    sem_init(&sem, 0, 1);  // 必须先初始化
    sem_wait(&sem);        // 然后才能使用
    // ... 使用信号量
    sem_post(&sem);
    sem_destroy(&sem);     // 最后销毁
}

2. 避免死锁

c 复制代码
// 危险示例:可能导致死锁
sem_t sem1, sem2;

void* thread1(void* arg) {
    sem_wait(&sem1);
    sleep(1);  // 模拟处理时间,增加死锁概率
    sem_wait(&sem2);  // 可能在这里死锁
    
    // 临界区代码...
    
    sem_post(&sem2);
    sem_post(&sem1);
    return NULL;
}

void* thread2(void* arg) {
    sem_wait(&sem2);  // 与thread1相反的获取顺序
    sleep(1);
    sem_wait(&sem1);  // 死锁点
    
    // 临界区代码...
    
    sem_post(&sem1);
    sem_post(&sem2);
    return NULL;
}

// 解决方案:统一的获取顺序
void* safe_thread1(void* arg) {
    sem_wait(&sem1);
    sem_wait(&sem2);  // 总是先获取sem1,再获取sem2
    
    // 临界区代码...
    
    sem_post(&sem2);
    sem_post(&sem1);
    return NULL;
}

void* safe_thread2(void* arg) {
    sem_wait(&sem1);  // 与thread1相同的获取顺序
    sem_wait(&sem2);
    
    // 临界区代码...
    
    sem_post(&sem2);
    sem_post(&sem1);
    return NULL;
}

易错点及解决方案

1. 忘记释放信号量

c 复制代码
// 错误示例:异常路径下忘记释放信号量
void risky_function(int value) {
    sem_wait(&resource_sem);
    
    if (value < 0) {
        return;  // 错误!这里直接返回,信号量没有释放!
    }
    
    if (some_error_condition) {
        return;  // 错误!另一个泄漏点!
    }
    
    // 正常处理...
    sem_post(&resource_sem);  // 只有正常路径会释放
}

// 正确解决方案1:使用goto统一清理
void safe_function1(int value) {
    sem_wait(&resource_sem);
    
    if (value < 0) {
        goto cleanup;  // 跳转到清理代码
    }
    
    if (some_error_condition) {
        goto cleanup;
    }
    
    // 正常处理...
    
cleanup:
    sem_post(&resource_sem);
}

// 正确解决方案2:使用do-while(0)模式
void safe_function2(int value) {
    sem_wait(&resource_sem);
    
    do {
        if (value < 0) {
            break;
        }
        
        if (some_error_condition) {
            break;
        }
        
        // 正常处理...
    } while(0);
    
    sem_post(&resource_sem);  // 无论哪种路径都会执行到这里
}

2. 信号中断处理

c 复制代码
#include <signal.h>

volatile sig_atomic_t interrupted = 0;

void signal_handler(int sig) {
    interrupted = 1;
}

// 正确处理信号中断的sem_wait
int robust_sem_wait(sem_t *sem) {
    int result;
    
    while ((result = sem_wait(sem)) == -1 && errno == EINTR) {
        if (interrupted) {
            printf("收到中断信号,退出等待\n");
            return -1;
        }
        // 继续等待
        printf("sem_wait被非致命信号中断,继续等待...\n");
    }
    
    return result;
}

void* interruptible_worker(void* arg) {
    printf("线程开始工作...\n");
    
    if (robust_sem_wait(&resource_sem) == 0) {
        printf("成功获取信号量\n");
        
        // 检查是否在等待过程中被中断
        if (interrupted) {
            printf("在等待过程中收到中断信号\n");
            sem_post(&resource_sem);
            return NULL;
        }
        
        // 正常处理...
        sleep(2);
        
        sem_post(&resource_sem);
        printf("工作完成\n");
    }
    
    return NULL;
}

使用细节

1. 与sem_trywait()的区别

c 复制代码
#include <stdio.h>

// sem_wait: 阻塞等待
// sem_trywait: 非阻塞尝试
void demonstrate_difference() {
    sem_t sem;
    sem_init(&sem, 0, 0);  // 初始值为0,表示无可用资源
    
    // 尝试非阻塞获取
    if (sem_trywait(&sem) == -1) {
        if (errno == EAGAIN) {
            printf("sem_trywait: 信号量不可用,立即返回\n");
        }
    }
    
    // 在另一个线程中post信号量
    pthread_t tid;
    pthread_create(&tid, NULL, void* arg -> void* {
        sleep(1);
        sem_post((sem_t*)arg);
        return NULL;
    }, &sem);
    
    printf("sem_wait: 阻塞等待信号量...\n");
    sem_wait(&sem);  // 这里会阻塞直到另一个线程post
    printf("sem_wait: 成功获取信号量\n");
    
    pthread_join(tid, NULL);
    sem_destroy(&sem);
}

2. 超时版本的sem_timedwait()

c 复制代码
#include <time.h>
#include <stdio.h>

void demonstrate_timedwait() {
    sem_t sem;
    sem_init(&sem, 0, 0);  // 初始为0,需要等待
    
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += 2;  // 设置2秒超时
    
    printf("等待信号量,超时时间2秒...\n");
    
    int result = sem_timedwait(&sem, &ts);
    if (result == -1) {
        if (errno == ETIMEDOUT) {
            printf("sem_timedwait: 等待超时\n");
        } else {
            perror("sem_timedwait失败");
        }
    } else {
        printf("sem_timedwait: 成功获取信号量\n");
        sem_post(&sem);
    }
    
    sem_destroy(&sem);
}

完整生产者和消费者示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>

#define BUFFER_SIZE 5
#define PRODUCER_COUNT 3
#define CONSUMER_COUNT 2
#define ITEMS_PER_PRODUCER 4

typedef struct {
    int buffer[BUFFER_SIZE];
    int in, out, count;
    sem_t mutex;        // 保护缓冲区的互斥信号量
    sem_t empty;        // 空槽位计数
    sem_t full;         // 满槽位计数
} bounded_buffer_t;

bounded_buffer_t bb;

void buffer_init(bounded_buffer_t *buf) {
    buf->in = buf->out = buf->count = 0;
    sem_init(&buf->mutex, 0, 1);
    sem_init(&buf->empty, 0, BUFFER_SIZE);
    sem_init(&buf->full, 0, 0);
}

void buffer_destroy(bounded_buffer_t *buf) {
    sem_destroy(&buf->mutex);
    sem_destroy(&buf->empty);
    sem_destroy(&buf->full);
}

void produce_item(bounded_buffer_t *buf, int item, int producer_id) {
    // 等待空槽位
    sem_wait(&buf->empty);
    
    // 保护缓冲区访问
    sem_wait(&buf->mutex);
    
    // 生产项目
    buf->buffer[buf->in] = item;
    buf->in = (buf->in + 1) % BUFFER_SIZE;
    buf->count++;
    
    printf("生产者%d: 生产项目%d, 缓冲区中有%d个项目\n", 
           producer_id, item, buf->count);
    
    sem_post(&buf->mutex);
    sem_post(&buf->full);  // 增加满槽位计数
}

int consume_item(bounded_buffer_t *buf, int consumer_id) {
    // 等待有项目可消费
    sem_wait(&buf->full);
    
    // 保护缓冲区访问
    sem_wait(&buf->mutex);
    
    // 消费项目
    int item = buf->buffer[buf->out];
    buf->out = (buf->out + 1) % BUFFER_SIZE;
    buf->count--;
    
    printf("消费者%d: 消费项目%d, 缓冲区中有%d个项目\n", 
           consumer_id, item, buf->count);
    
    sem_post(&buf->mutex);
    sem_post(&buf->empty);  // 增加空槽位计数
    
    return item;
}

void* producer(void *arg) {
    int producer_id = *(int*)arg;
    
    for (int i = 0; i < ITEMS_PER_PRODUCER; i++) {
        int item = producer_id * 100 + i;
        produce_item(&bb, item, producer_id);
        sleep(1);  // 模拟生产时间
    }
    
    printf("生产者%d: 完成生产\n", producer_id);
    return NULL;
}

void* consumer(void *arg) {
    int consumer_id = *(int*)arg;
    
    for (int i = 0; i < (ITEMS_PER_PRODUCER * PRODUCER_COUNT) / CONSUMER_COUNT; i++) {
        int item = consume_item(&bb, consumer_id);
        sleep(2);  // 模拟消费时间
    }
    
    printf("消费者%d: 完成消费\n", consumer_id);
    return NULL;
}

int main() {
    pthread_t producers[PRODUCER_COUNT];
    pthread_t consumers[CONSUMER_COUNT];
    int producer_ids[PRODUCER_COUNT];
    int consumer_ids[CONSUMER_COUNT];
    
    // 初始化缓冲区
    buffer_init(&bb);
    
    // 创建生产者线程
    for (int i = 0; i < PRODUCER_COUNT; i++) {
        producer_ids[i] = i + 1;
        pthread_create(&producers[i], NULL, producer, &producer_ids[i]);
    }
    
    // 创建消费者线程
    for (int i = 0; i < CONSUMER_COUNT; i++) {
        consumer_ids[i] = i + 1;
        pthread_create(&consumers[i], NULL, consumer, &consumer_ids[i]);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < PRODUCER_COUNT; i++) {
        pthread_join(producers[i], NULL);
    }
    for (int i = 0; i < CONSUMER_COUNT; i++) {
        pthread_join(consumers[i], NULL);
    }
    
    // 清理资源
    buffer_destroy(&bb);
    printf("所有生产者和消费者线程完成\n");
    
    return 0;
}

总结

sem_wait() 是POSIX信号量编程中最关键的函数之一,使用时需要注意:

  1. 初始化前置:确保信号量已正确初始化
  2. 错误处理:检查返回值,特别是EINTR处理
  3. 资源释放:确保所有路径都释放信号量,避免死锁
  4. 信号安全:在信号处理程序中避免使用
  5. 超时考虑:长时间阻塞时考虑使用sem_timedwait()

正确使用sem_wait()可以构建高效可靠的同步机制,是多线程编程的重要基础。



Linux系统中sem_post()函数

函数声明

c 复制代码
#include <semaphore.h>

int sem_post(sem_t *sem);

函数参数详解

sem - 信号量指针

  • 作用:指向已初始化的信号量对象
  • 类型sem_t *
  • 要求:必须指向有效的、已初始化的信号量
c 复制代码
#include <semaphore.h>
#include <stdio.h>

// 正确使用示例
sem_t my_sem;

void init_and_use_semaphore() {
    // 1. 先初始化信号量
    if (sem_init(&my_sem, 0, 0) == -1) {  // 初始值为0
        perror("sem_init failed");
        return;
    }
    
    // 2. 在某个条件满足时post信号量
    printf("准备释放信号量...\n");
    if (sem_post(&my_sem) == -1) {
        perror("sem_post failed");
    } else {
        printf("信号量释放成功\n");
    }
}

函数返回值

  • 成功 :返回 0
  • 失败 :返回 -1,并设置相应的 errno

常见错误码:

  • EINVAL:参数sem不是有效的信号量
  • EOVERFLOW:信号量值超过上限(理论可能,实际很少见)
c 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>

void safe_sem_post(sem_t *sem) {
    if (sem_post(sem) == -1) {
        switch(errno) {
            case EINVAL:
                printf("错误:无效的信号量指针\n");
                break;
            case EOVERFLOW:
                printf("错误:信号量值超过上限\n");
                break;
            default:
                printf("未知错误: %s\n", strerror(errno));
        }
    } else {
        printf("信号量释放成功\n");
    }
}

函数作用

sem_post() 是POSIX信号量的核心操作函数,主要作用:

  1. 原子性加一操作:将信号量的值加1
  2. 唤醒等待者:如果有线程/进程正在等待该信号量,则唤醒其中一个
  3. 同步通知:用于通知其他线程/进程资源可用或条件满足
c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

sem_t data_ready;
int shared_data = 0;

void* producer_thread(void* arg) {
    printf("生产者: 开始生产数据...\n");
    sleep(2);  // 模拟数据生产时间
    
    // 生产数据
    shared_data = 42;
    printf("生产者: 数据生产完成,值=%d\n", shared_data);
    
    // 通知消费者数据已就绪
    sem_post(&data_ready);
    printf("生产者: 已通知消费者\n");
    
    return NULL;
}

void* consumer_thread(void* arg) {
    printf("消费者: 等待数据...\n");
    
    // 等待数据就绪
    sem_wait(&data_ready);
    
    printf("消费者: 收到通知,读取数据=%d\n", shared_data);
    
    return NULL;
}

int main() {
    pthread_t producer, consumer;
    
    // 初始化信号量,初始值为0(表示数据未就绪)
    sem_init(&data_ready, 0, 0);
    
    pthread_create(&consumer, NULL, consumer_thread, NULL);
    sleep(1);  // 确保消费者先开始等待
    pthread_create(&producer, NULL, producer_thread, NULL);
    
    pthread_join(producer, NULL);
    pthread_join(consumer, NULL);
    
    sem_destroy(&data_ready);
    return 0;
}

使用注意事项

1. 信号量必须先初始化

c 复制代码
// 错误示例:使用未初始化的信号量
sem_t uninitialized_sem;

void wrong_usage() {
    // 未定义行为!信号量未初始化
    sem_post(&uninitialized_sem);  // 可能导致段错误
}

// 正确示例
void correct_usage() {
    sem_t sem;
    sem_init(&sem, 0, 0);  // 必须先初始化
    // ... 一些操作
    sem_post(&sem);        // 然后才能使用
    sem_destroy(&sem);     // 最后销毁
}

2. 避免过度发布(overflow)

c 复制代码
// 理论上可能的溢出问题(实际中很少见)
#define SEM_VALUE_MAX 2147483647  // 典型上限值

void demonstrate_overflow_risk() {
    sem_t sem;
    sem_init(&sem, 0, SEM_VALUE_MAX - 1);
    
    sem_post(&sem);  // 达到上限
    printf("信号量值达到上限\n");
    
    // 再次post可能导致溢出(行为未定义)
    // sem_post(&sem);  // 危险!
}

易错点及解决方案

1. 忘记调用sem_post(死锁)

c 复制代码
// 错误示例:异常路径下忘记释放信号量
sem_t resource_sem;

void risky_critical_section(int condition) {
    sem_wait(&resource_sem);
    
    if (condition < 0) {
        return;  // 错误!这里直接返回,信号量没有释放!
    }
    
    if (some_complex_operation_fails()) {
        return;  // 错误!另一个泄漏点!
    }
    
    // 正常处理...
    sem_post(&resource_sem);  // 只有正常路径会释放
}

// 正确解决方案1:使用goto统一清理
void safe_critical_section1(int condition) {
    sem_wait(&resource_sem);
    
    if (condition < 0) {
        goto cleanup;
    }
    
    if (some_complex_operation_fails()) {
        goto cleanup;
    }
    
    // 正常处理...
    
cleanup:
    sem_post(&resource_sem);
}

// 正确解决方案2:RAII包装器
typedef struct {
    sem_t *sem;
} sem_guard_t;

void sem_guard_init(sem_guard_t *guard, sem_t *sem) {
    sem_wait(sem);
    guard->sem = sem;
}

void sem_guard_release(sem_guard_t *guard) {
    if (guard->sem) {
        sem_post(guard->sem);
        guard->sem = NULL;
    }
}

void safe_critical_section2(int condition) {
    sem_guard_t guard;
    sem_guard_init(&guard, &resource_sem);
    
    // 无论发生什么,guard离开作用域时会自动释放
    if (condition < 0) {
        return;
    }
    
    if (some_complex_operation_fails()) {
        return;
    }
    
    // 正常处理...
    sem_guard_release(&guard);
}

2. 错误的发布顺序导致逻辑错误

c 复制代码
// 错误示例:发布顺序不当
sem_t step1_done, step2_done;

void* incorrect_worker(void* arg) {
    // 步骤1
    printf("执行步骤1\n");
    sem_post(&step1_done);  // 过早通知
    
    // 步骤2(可能耗时)
    sleep(2);
    printf("执行步骤2\n");
    sem_post(&step2_done);
    
    return NULL;
}

void* incorrect_coordinator(void* arg) {
    sem_wait(&step1_done);
    printf("协调器: 收到步骤1完成通知\n");
    
    // 这里假设步骤2已经完成,但实际上可能还没有
    sem_wait(&step2_done);
    printf("协调器: 收到步骤2完成通知\n");
    
    return NULL;
}

// 正确示例:在真正完成时发布
void* correct_worker(void* arg) {
    // 步骤1
    printf("执行步骤1\n");
    // 不要立即post,等所有相关工作完成
    
    // 步骤2
    sleep(2);
    printf("执行步骤2\n");
    
    // 所有步骤完成后一次性通知
    sem_post(&step1_done);
    sem_post(&step2_done);
    
    return NULL;
}

使用细节

1. 与sem_wait()的配对使用

c 复制代码
#include <stdio.h>
#include <pthread.h>

// 展示正确的配对使用
sem_t counter_sem;
int counter = 0;

void* increment_thread(void* arg) {
    for (int i = 0; i < 1000; i++) {
        sem_wait(&counter_sem);  // 获取访问权
        counter++;               // 临界区操作
        sem_post(&counter_sem);  // 释放访问权
    }
    return NULL;
}

void test_pairing() {
    pthread_t t1, t2;
    sem_init(&counter_sem, 0, 1);  // 二进制信号量
    
    pthread_create(&t1, NULL, increment_thread, NULL);
    pthread_create(&t2, NULL, increment_thread, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("最终计数器值: %d (应该是2000)\n", counter);
    sem_destroy(&counter_sem);
}

2. 在条件变量模式中的使用

c 复制代码
#include <pthread.h>

// 使用信号量实现简单的条件变量模式
sem_t condition_met;
int condition_flag = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* waiter_thread(void* arg) {
    printf("等待线程: 等待条件满足...\n");
    
    sem_wait(&condition_met);  // 等待条件
    
    pthread_mutex_lock(&mutex);
    printf("等待线程: 条件已满足,flag=%d\n", condition_flag);
    pthread_mutex_unlock(&mutex);
    
    return NULL;
}

void* setter_thread(void* arg) {
    sleep(2);  // 模拟准备工作
    
    pthread_mutex_lock(&mutex);
    condition_flag = 1;
    printf("设置线程: 条件已设置\n");
    pthread_mutex_unlock(&mutex);
    
    // 通知所有等待者
    sem_post(&condition_met);
    printf("设置线程: 已通知等待者\n");
    
    return NULL;
}

完整的生产者-消费者示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <time.h>

#define BUFFER_SIZE 5
#define PRODUCER_COUNT 2
#define CONSUMER_COUNT 2
#define TOTAL_ITEMS 10

typedef struct {
    int items[BUFFER_SIZE];
    int count;
    int in, out;
    sem_t mutex;        // 保护缓冲区的互斥信号量
    sem_t empty_slots;  // 空槽位计数
    sem_t full_slots;   // 满槽位计数
} thread_safe_buffer;

thread_safe_buffer buffer;

void buffer_init(thread_safe_buffer *buf) {
    buf->count = 0;
    buf->in = buf->out = 0;
    
    // 初始化信号量
    sem_init(&buf->mutex, 0, 1);           // 互斥信号量
    sem_init(&buf->empty_slots, 0, BUFFER_SIZE);  // 初始有空槽位
    sem_init(&buf->full_slots, 0, 0);      // 初始无满槽位
}

void buffer_destroy(thread_safe_buffer *buf) {
    sem_destroy(&buf->mutex);
    sem_destroy(&buf->empty_slots);
    sem_destroy(&buf->full_slots);
}

void buffer_produce(thread_safe_buffer *buf, int item, int producer_id) {
    // 等待空槽位
    sem_wait(&buf->empty_slots);
    
    // 保护缓冲区操作
    sem_wait(&buf->mutex);
    
    // 生产项目
    buf->items[buf->in] = item;
    buf->in = (buf->in + 1) % BUFFER_SIZE;
    buf->count++;
    
    printf("生产者%d: 生产项目%d, 缓冲区大小=%d/%d\n", 
           producer_id, item, buf->count, BUFFER_SIZE);
    
    sem_post(&buf->mutex);
    
    // 通知消费者有新项目可用
    sem_post(&buf->full_slots);
}

int buffer_consume(thread_safe_buffer *buf, int consumer_id) {
    // 等待有项目可消费
    sem_wait(&buf->full_slots);
    
    // 保护缓冲区操作
    sem_wait(&buf->mutex);
    
    // 消费项目
    int item = buf->items[buf->out];
    buf->out = (buf->out + 1) % BUFFER_SIZE;
    buf->count--;
    
    printf("消费者%d: 消费项目%d, 缓冲区大小=%d/%d\n", 
           consumer_id, item, buf->count, BUFFER_SIZE);
    
    sem_post(&buf->mutex);
    
    // 通知生产者有空槽位
    sem_post(&buf->empty_slots);
    
    return item;
}

void* producer(void *arg) {
    int producer_id = *(int*)arg;
    int items_produced = 0;
    
    while (items_produced < TOTAL_ITEMS / PRODUCER_COUNT) {
        int item = rand() % 1000;
        
        buffer_produce(&buffer, item, producer_id);
        items_produced++;
        
        // 随机延迟,模拟生产时间
        usleep(rand() % 1000000);
    }
    
    printf("生产者%d: 完成,共生产%d个项目\n", producer_id, items_produced);
    return NULL;
}

void* consumer(void *arg) {
    int consumer_id = *(int*)arg;
    int items_consumed = 0;
    
    while (items_consumed < TOTAL_ITEMS / CONSUMER_COUNT) {
        int item = buffer_consume(&buffer, consumer_id);
        items_consumed++;
        
        // 随机延迟,模拟消费时间
        usleep(rand() % 1500000);
    }
    
    printf("消费者%d: 完成,共消费%d个项目\n", consumer_id, items_consumed);
    return NULL;
}

int main() {
    pthread_t producers[PRODUCER_COUNT];
    pthread_t consumers[CONSUMER_COUNT];
    int producer_ids[PRODUCER_COUNT];
    int consumer_ids[CONSUMER_COUNT];
    
    srand(time(NULL));
    
    // 初始化缓冲区
    buffer_init(&buffer);
    
    printf("开始生产者-消费者演示...\n");
    printf("缓冲区大小: %d, 生产者: %d, 消费者: %d, 总项目数: %d\n\n",
           BUFFER_SIZE, PRODUCER_COUNT, CONSUMER_COUNT, TOTAL_ITEMS);
    
    // 创建生产者线程
    for (int i = 0; i < PRODUCER_COUNT; i++) {
        producer_ids[i] = i + 1;
        pthread_create(&producers[i], NULL, producer, &producer_ids[i]);
    }
    
    // 创建消费者线程
    for (int i = 0; i < CONSUMER_COUNT; i++) {
        consumer_ids[i] = i + 1;
        pthread_create(&consumers[i], NULL, consumer, &consumer_ids[i]);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < PRODUCER_COUNT; i++) {
        pthread_join(producers[i], NULL);
    }
    for (int i = 0; i < CONSUMER_COUNT; i++) {
        pthread_join(consumers[i], NULL);
    }
    
    // 清理资源
    buffer_destroy(&buffer);
    printf("\n所有线程完成,程序结束\n");
    
    return 0;
}

高级用法:信号量链

c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define STAGE_COUNT 3

sem_t stage_semaphores[STAGE_COUNT];

// 多阶段流水线处理
void* pipeline_stage(void* arg) {
    int stage_id = *(int*)arg;
    int next_stage = (stage_id + 1) % STAGE_COUNT;
    
    printf("阶段%d: 启动\n", stage_id);
    
    for (int i = 0; i < 3; i++) {
        // 等待前一个阶段完成
        sem_wait(&stage_semaphores[stage_id]);
        
        printf("阶段%d: 处理项目%d\n", stage_id, i);
        
        // 模拟处理时间
        usleep(500000);
        
        // 通知下一个阶段
        sem_post(&stage_semaphores[next_stage]);
        printf("阶段%d: 将项目%d传递给阶段%d\n", stage_id, i, next_stage);
    }
    
    printf("阶段%d: 完成\n", stage_id);
    return NULL;
}

void demonstrate_pipeline() {
    pthread_t stages[STAGE_COUNT];
    int stage_ids[STAGE_COUNT];
    
    // 初始化信号量链
    for (int i = 0; i < STAGE_COUNT; i++) {
        sem_init(&stage_semaphores[i], 0, 0);
    }
    
    // 启动第一个项目
    sem_post(&stage_semaphores[0]);
    
    // 创建流水线阶段线程
    for (int i = 0; i < STAGE_COUNT; i++) {
        stage_ids[i] = i;
        pthread_create(&stages[i], NULL, pipeline_stage, &stage_ids[i]);
    }
    
    // 等待所有阶段完成
    for (int i = 0; i < STAGE_COUNT; i++) {
        pthread_join(stages[i], NULL);
    }
    
    // 清理
    for (int i = 0; i < STAGE_COUNT; i++) {
        sem_destroy(&stage_semaphores[i]);
    }
}

总结

sem_post() 是POSIX信号量编程中的关键函数,使用时需要注意:

  1. 初始化前置:确保信号量已正确初始化
  2. 配对使用:与sem_wait()正确配对,避免死锁
  3. 异常安全:确保所有执行路径都调用sem_post()
  4. 发布时机:在适当的时机发布信号量,避免逻辑错误
  5. 资源清理:程序结束时销毁信号量

正确使用sem_post()可以实现复杂的同步模式,是多线程编程的重要基础。



Linux系统中sem_getvalue()函数

函数声明

c 复制代码
#include <semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);

函数参数详解

1. sem - 信号量指针

  • 作用:指向已初始化的信号量对象
  • 类型sem_t *
  • 要求:必须指向有效的、已初始化的信号量

2. sval - 值存储指针

  • 作用:指向存储信号量当前值的整数变量
  • 类型int *
  • 注意:必须指向有效的内存地址

函数返回值

  • 成功 :返回 0
  • 失败 :返回 -1,并设置相应的 errno

常见错误码:

  • EINVAL:参数sem不是有效的信号量

函数作用

sem_getvalue() 用于获取信号量的当前值,主要用于:

  1. 监控和调试:查看信号量的状态
  2. 资源监控:了解可用资源数量
  3. 性能分析:监控系统负载情况
  4. 死锁检测:辅助诊断同步问题

基本使用示例

c 复制代码
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>

sem_t my_sem;

void print_sem_value(const char* message) {
    int value;
    if (sem_getvalue(&my_sem, &value) == 0) {
        printf("%s: 信号量值 = %d\n", message, value);
    } else {
        perror("sem_getvalue失败");
    }
}

int main() {
    // 初始化信号量
    if (sem_init(&my_sem, 0, 3) == -1) {
        perror("sem_init失败");
        return 1;
    }
    
    print_sem_value("初始化后");
    
    // 模拟一些操作
    sem_wait(&my_sem);
    print_sem_value("第一次wait后");
    
    sem_wait(&my_sem);
    print_sem_value("第二次wait后");
    
    sem_post(&my_sem);
    print_sem_value("一次post后");
    
    sem_destroy(&my_sem);
    return 0;
}

输出示例:

复制代码
初始化后: 信号量值 = 3
第一次wait后: 信号量值 = 2
第二次wait后: 信号量值 = 1
一次post后: 信号量值 = 2

完整使用场景示例

1. 资源池监控系统

c 复制代码
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define POOL_SIZE 5

sem_t connection_pool;
int active_connections = 0;

// 监控线程:定期检查资源池状态
void* monitor_thread(void* arg) {
    while (1) {
        int available;
        sem_getvalue(&connection_pool, &available);
        
        time_t now = time(NULL);
        struct tm* timeinfo = localtime(&now);
        
        printf("[%02d:%02d:%02d] 监控报告: ", 
               timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
        printf("可用连接: %d/%d, 活跃连接: %d\n", 
               available, POOL_SIZE, active_connections);
        
        // 预警机制
        if (available == 0) {
            printf("⚠️  警告: 连接池已空!\n");
        }
        if (available == POOL_SIZE) {
            printf("✅ 连接池空闲\n");
        }
        
        sleep(2);  // 每2秒监控一次
    }
    return NULL;
}

// 模拟客户端连接
void* client_thread(void* arg) {
    int client_id = *(int*)arg;
    
    printf("客户端 %d 尝试获取连接...\n", client_id);
    
    sem_wait(&connection_pool);
    active_connections++;
    
    int available;
    sem_getvalue(&connection_pool, &available);
    printf("客户端 %d 获得连接,剩余可用: %d\n", client_id, available);
    
    // 模拟工作
    sleep(1 + (rand() % 3));
    
    sem_post(&connection_pool);
    active_connections--;
    
    sem_getvalue(&connection_pool, &available);
    printf("客户端 %d 释放连接,剩余可用: %d\n", client_id, available);
    
    return NULL;
}

int main() {
    pthread_t monitor, clients[10];
    int client_ids[10];
    
    srand(time(NULL));
    
    // 初始化连接池信号量
    sem_init(&connection_pool, 0, POOL_SIZE);
    
    printf("=== 连接池监控系统启动 ===\n");
    printf("连接池大小: %d\n\n", POOL_SIZE);
    
    // 启动监控线程
    pthread_create(&monitor, NULL, monitor_thread, NULL);
    
    // 模拟多个客户端
    for (int i = 0; i < 10; i++) {
        client_ids[i] = i + 1;
        pthread_create(&clients[i], NULL, client_thread, &client_ids[i]);
        usleep(500000);  // 间隔0.5秒
    }
    
    // 等待所有客户端完成
    for (int i = 0; i < 10; i++) {
        pthread_join(clients[i], NULL);
    }
    
    // 停止监控(在实际应用中需要更优雅的停止机制)
    pthread_cancel(monitor);
    pthread_join(monitor, NULL);
    
    sem_destroy(&connection_pool);
    printf("\n=== 系统关闭 ===\n");
    
    return 0;
}

2. 生产者-消费者队列监控

c 复制代码
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>

#define BUFFER_SIZE 10

typedef struct {
    sem_t empty;    // 空槽位信号量
    sem_t full;     // 满槽位信号量
    int buffer[BUFFER_SIZE];
    int in, out;
    pthread_mutex_t mutex;
} bounded_buffer;

bounded_buffer bb;

void buffer_init(bounded_buffer* buf) {
    sem_init(&buf->empty, 0, BUFFER_SIZE);
    sem_init(&buf->full, 0, 0);
    buf->in = buf->out = 0;
    pthread_mutex_init(&buf->mutex, NULL);
}

void buffer_destroy(bounded_buffer* buf) {
    sem_destroy(&buf->empty);
    sem_destroy(&buf->full);
    pthread_mutex_destroy(&buf->mutex);
}

// 获取缓冲区状态
void get_buffer_status(bounded_buffer* buf, int* empty_slots, int* full_slots) {
    sem_getvalue(&buf->empty, empty_slots);
    sem_getvalue(&buf->full, full_slots);
}

void* producer(void* arg) {
    int producer_id = *(int*)arg;
    int item = 0;
    
    while (item < 20) {  // 每个生产者生产20个物品
        int empty, full;
        
        // 生产前检查状态
        get_buffer_status(&bb, &empty, &full);
        printf("生产者%d: 准备生产,缓冲区状态 - 空位:%d, 满位:%d\n", 
               producer_id, empty, full);
        
        sem_wait(&bb.empty);
        pthread_mutex_lock(&bb.mutex);
        
        // 生产物品
        bb.buffer[bb.in] = ++item;
        bb.in = (bb.in + 1) % BUFFER_SIZE;
        
        pthread_mutex_unlock(&bb.mutex);
        sem_post(&bb.full);
        
        // 生产后检查状态
        get_buffer_status(&bb, &empty, &full);
        printf("生产者%d: 生产物品%d完成,缓冲区状态 - 空位:%d, 满位:%d\n", 
               producer_id, item, empty, full);
        
        usleep(100000 + rand() % 200000);  // 随机延迟
    }
    
    printf("生产者%d: 生产完成\n", producer_id);
    return NULL;
}

void* consumer(void* arg) {
    int consumer_id = *(int*)arg;
    int consumed = 0;
    
    while (consumed < 15) {  // 每个消费者消费15个物品
        int empty, full;
        
        // 消费前检查状态
        get_buffer_status(&bb, &empty, &full);
        printf("消费者%d: 准备消费,缓冲区状态 - 空位:%d, 满位:%d\n", 
               consumer_id, empty, full);
        
        sem_wait(&bb.full);
        pthread_mutex_lock(&bb.mutex);
        
        // 消费物品
        int item = bb.buffer[bb.out];
        bb.out = (bb.out + 1) % BUFFER_SIZE;
        consumed++;
        
        pthread_mutex_unlock(&bb.mutex);
        sem_post(&bb.empty);
        
        // 消费后检查状态
        get_buffer_status(&bb, &empty, &full);
        printf("消费者%d: 消费物品%d完成,缓冲区状态 - 空位:%d, 满位:%d\n", 
               consumer_id, item, empty, full);
        
        usleep(150000 + rand() % 250000);  // 随机延迟
    }
    
    printf("消费者%d: 消费完成\n", consumer_id);
    return NULL;
}

// 监控线程
void* monitor_thread(void* arg) {
    int iteration = 0;
    while (iteration++ < 10) {  // 监控10次
        int empty, full;
        get_buffer_status(&bb, &empty, &full);
        
        printf("\n=== 监控报告 [%d] ===\n", iteration);
        printf("空槽位: %d/%d\n", empty, BUFFER_SIZE);
        printf("满槽位: %d/%d\n", full, BUFFER_SIZE);
        printf("利用率: %.1f%%\n", (double)full / BUFFER_SIZE * 100);
        
        // 性能建议
        if (empty == BUFFER_SIZE) {
            printf("建议: 增加消费者数量\n");
        } else if (full == BUFFER_SIZE) {
            printf("建议: 增加生产者数量\n");
        } else if (empty > full) {
            printf("状态: 生产速度 > 消费速度\n");
        } else {
            printf("状态: 消费速度 > 生产速度\n");
        }
        printf("==================\n\n");
        
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t producers[2], consumers[3], monitor;
    int producer_ids[] = {1, 2};
    int consumer_ids[] = {1, 2, 3};
    
    srand(time(NULL));
    buffer_init(&bb);
    
    printf("=== 生产者-消费者系统启动 ===\n");
    printf("缓冲区大小: %d\n\n", BUFFER_SIZE);
    
    // 启动监控线程
    pthread_create(&monitor, NULL, monitor_thread, NULL);
    
    // 启动生产者
    for (int i = 0; i < 2; i++) {
        pthread_create(&producers[i], NULL, producer, &producer_ids[i]);
    }
    
    // 启动消费者
    for (int i = 0; i < 3; i++) {
        pthread_create(&consumers[i], NULL, consumer, &consumer_ids[i]);
    }
    
    // 等待线程完成
    for (int i = 0; i < 2; i++) {
        pthread_join(producers[i], NULL);
    }
    for (int i = 0; i < 3; i++) {
        pthread_join(consumers[i], NULL);
    }
    pthread_join(monitor, NULL);
    
    buffer_destroy(&bb);
    printf("=== 系统关闭 ===\n");
    
    return 0;
}

使用注意事项

1. 竞态条件问题

c 复制代码
// ❌ 错误示例:竞态条件
void unsafe_check(sem_t* sem) {
    int value;
    if (sem_getvalue(sem, &value) == 0) {
        if (value > 0) {
            // 这里其他线程可能已经修改了信号量值!
            sem_wait(sem);  // 可能阻塞!
        }
    }
}

// ✅ 正确示例:直接使用信号量操作
void safe_approach(sem_t* sem) {
    // 直接尝试获取,让信号量机制处理同步
    if (sem_trywait(sem) == 0) {
        // 成功获取
    } else {
        // 无法获取(值为0或有等待者)
    }
}

2. 错误处理最佳实践

c 复制代码
#include <errno.h>

// 健壮的sem_getvalue封装
int robust_sem_getvalue(sem_t* sem, int* value) {
    if (sem == NULL || value == NULL) {
        errno = EINVAL;
        return -1;
    }
    
    int result = sem_getvalue(sem, value);
    if (result == -1) {
        switch(errno) {
            case EINVAL:
                fprintf(stderr, "错误: 无效的信号量指针\n");
                break;
            default:
                perror("sem_getvalue失败");
        }
        return -1;
    }
    
    return 0;
}

// 带重试的获取函数
int get_sem_value_with_retry(sem_t* sem, int* value, int max_retries) {
    for (int i = 0; i < max_retries; i++) {
        if (robust_sem_getvalue(sem, value) == 0) {
            return 0;  // 成功
        }
        
        if (errno != EINTR) {  // 如果不是被信号中断,则退出重试
            break;
        }
        
        usleep(100000);  // 等待100ms后重试
    }
    
    return -1;  // 重试多次后仍然失败
}

3. 性能监控工具类

c 复制代码
#include <sys/time.h>

// 信号量性能监控器
typedef struct {
    sem_t* sem;
    char* name;
    long monitor_interval_us;
    int is_running;
    pthread_t thread_id;
} sem_monitor_t;

void* monitor_function(void* arg) {
    sem_monitor_t* monitor = (sem_monitor_t*)arg;
    struct timeval start, end;
    
    while (monitor->is_running) {
        gettimeofday(&start, NULL);
        
        int value;
        if (sem_getvalue(monitor->sem, &value) == 0) {
            gettimeofday(&end, NULL);
            long response_time = (end.tv_sec - start.tv_sec) * 1000000 + 
                                (end.tv_usec - start.tv_usec);
            
            printf("监控[%s]: 值=%d, 响应时间=%ld微秒\n", 
                   monitor->name, value, response_time);
        }
        
        usleep(monitor->monitor_interval_us);
    }
    
    return NULL;
}

// 启动监控
int start_monitoring(sem_monitor_t* monitor) {
    monitor->is_running = 1;
    return pthread_create(&monitor->thread_id, NULL, monitor_function, monitor);
}

// 停止监控
void stop_monitoring(sem_monitor_t* monitor) {
    monitor->is_running = 0;
    pthread_join(monitor->thread_id, NULL);
}

易错点和解决方案

1. 信号量未初始化

c 复制代码
// ❌ 错误:使用未初始化的信号量
sem_t uninitialized_sem;
int value;
sem_getvalue(&uninitialized_sem, &value);  // 未定义行为!

// ✅ 正确:确保信号量已初始化
sem_t my_sem;
if (sem_init(&my_sem, 0, 5) == 0) {
    int value;
    if (sem_getvalue(&my_sem, &value) == 0) {
        printf("信号量值: %d\n", value);
    }
}

2. 指针有效性检查

c 复制代码
// ❌ 错误:无效指针
int* invalid_ptr = NULL;
sem_getvalue(&my_sem, invalid_ptr);  // 段错误!

// ✅ 正确:检查指针有效性
int value;
if (sem_getvalue(&my_sem, &value) == 0) {
    // 安全使用value
}

3. 多线程环境下的值过时问题

c 复制代码
// 注意:获取的值可能立即过时
void print_current_status(sem_t* sem) {
    int value1, value2;
    
    sem_getvalue(sem, &value1);
    // 在这里,其他线程可能已经修改了信号量值
    sem_getvalue(sem, &value2);
    
    printf("第一次获取: %d, 第二次获取: %d\n", value1, value2);
    // 两个值可能不同!
}

实际应用案例

1. 负载均衡器

c 复制代码
// 基于信号量值的简单负载均衡
typedef struct {
    sem_t* sems[10];  // 10个工作队列的信号量
    int queue_count;
} load_balancer_t;

int choose_best_queue(load_balancer_t* lb) {
    int best_queue = -1;
    int max_value = -1;
    
    for (int i = 0; i < lb->queue_count; i++) {
        int current_value;
        if (sem_getvalue(lb->sems[i], &current_value) == 0) {
            if (current_value > max_value) {
                max_value = current_value;
                best_queue = i;
            }
        }
    }
    
    return best_queue;  // 返回最空闲的队列
}

2. 系统健康检查

c 复制代码
// 系统健康监控
void system_health_check(sem_t* important_sems[], int sem_count) {
    printf("=== 系统健康检查 ===\n");
    
    for (int i = 0; i < sem_count; i++) {
        int value;
        if (sem_getvalue(important_sems[i], &value) == 0) {
            const char* status = (value > 0) ? "正常" : "警告";
            printf("信号量%d: 值=%d [%s]\n", i, value, status);
        } else {
            printf("信号量%d: 无法读取 [错误]\n", i);
        }
    }
    
    printf("==================\n");
}

总结

sem_getvalue() 是一个有用的诊断工具,但需要注意:

  1. 不是同步原语:不能用于同步控制
  2. 值可能过时:在多线程环境中获取的值可能立即失效
  3. 主要用于监控:适合调试、监控和性能分析
  4. 需要错误处理:始终检查返回值
  5. 注意可移植性:不同系统可能有不同的实现细节

最佳实践

  • 用于系统监控和调试
  • 结合其他诊断工具使用
  • 在生产代码中谨慎使用
  • 优先使用信号量操作(wait/post)进行同步
相关推荐
Hard but lovely2 小时前
linux: udp服务器与客户端 CS 基于ipv4的地址结构体
linux·服务器·udp
长沙红胖子Qt2 小时前
公司禅道笔记(一):公网服务器centos6上成功部署禅道20.8版本
服务器·禅道·研发管理·禅道云端部署
adnyting3 小时前
【Linux日新月异(六)】CentOS 7网络命令深度解析:从传统到现代网络管理
linux·网络·centos
java_logo3 小时前
MONGO-EXPRESS Docker 容器化部署指南
linux·运维·mongodb·docker·容器·express
Hi202402173 小时前
Ubuntu 主机序列号克隆指南:原理与实现
linux·运维·ubuntu
如果是君3 小时前
ubuntu20.04下使用D435i实时运行ORB-SLAM3
linux·ubuntu·orb-slam3·d435i
wsig3 小时前
linux下SO文件编译指定其他依赖库的路径
linux·运维·服务器
想唱rap4 小时前
Linux下进程的状态和优先级
linux·运维·服务器·开发语言·数据结构·算法
稚辉君.MCA_P8_Java4 小时前
Sqoop 实现的功能是什么
java·服务器·架构·kubernetes·sqoop