• 多进程编程- POSIX无名信号量


    基本概念

    无名信号量(也称为匿名信号量)是一个同步原语,通常用于线程之间的同步,而不是进程之间。与命名信号量(用于进程间同步)相比,无名信号量的生命周期通常受限于创建它的进程,并且它们不需要一个与系统相关的名字。

    以下是关于POSIX无名信号量的详细介绍:

    1. :信号量有一个关联的整数值,最初可以设为任意非负整数。
    2. P操作/等待:当线程尝试执行P操作(或称为等待/下降/获取操作)时,如果信号量的值大于0,则它将减少该值并继续。如果值是0,线程将阻塞,直到值变得非零。
    3. V操作/发出:线程执行V操作(或称为发出/增加/释放操作)将增加信号量的值。如果有线程正在因P操作而被阻塞,它们中的一个将被唤醒。

    主要函数

    1. 初始化sem_init

      int sem_init(sem_t *sem, int pshared, unsigned int value);
      
      • 1
      • sem: 指向要初始化的信号量的指针。
      • pshared: 如果为非零值,则信号量在进程间共享。如果为0,则只在进程的线程之间共享。
      • value: 信号量的初始值。
    2. 销毁sem_destroy

      int sem_destroy(sem_t *sem);
      
      • 1
      • 销毁之前使用sem_init初始化的信号量。
    3. 等待sem_wait

      int sem_wait(sem_t *sem);
      
      • 1
      • 减少信号量的值。如果值已经是0,那么调用线程将被阻塞,直到信号量的值变为正数。
    4. 尝试等待sem_trywait

      int sem_trywait(sem_t *sem);
      
      • 1
      • 尝试减少信号量的值。如果值为0,该函数立即返回,不会阻塞。
    5. 发出sem_post

      int sem_post(sem_t *sem);
      
      • 1
      • 增加信号量的值。如果有线程因sem_wait而被阻塞,其中一个将被唤醒。

    使用注意事项

    • 无名信号量主要用于线程间的同步。尽管pshared参数可以使它们在进程之间共享,但通常在这种情况下使用命名信号量更为合适。

    • 当不再需要信号量时,应使用sem_destroy销毁它,以释放相关的资源。

    • 和所有同步原语一样,使用信号量时需要注意避免死锁、活锁和竞争条件。

    无名信号量在多线程应用中非常有用,因为它们为线程提供了一种简单且有效的同步机制,特别是当需要对资源访问进行互斥或需要线程之间的协作时。

    示例

    我们来看一个简单的无名信号量的例子。这个例子中,有两个线程:一个生产者线程和一个消费者线程。生产者生成一个整数并将其存储在共享变量中,消费者读取这个整数。这两个线程通过无名信号量来同步,以确保生产者在消费者读取整数之前生产整数,消费者在生产者生产新的整数之前读取整数。

    #include 
    #include 
    #include 
    
    sem_t sem_prod, sem_cons;
    int shared_var;
    
    void *producer(void *arg) {
        for (int i = 1; i <= 5; i++) {
            sem_wait(&sem_prod); // Wait until the consumer has consumed the last item
            shared_var = i; // Produce an item
            printf("Produced: %d\n", shared_var);
            sem_post(&sem_cons); // Signal the consumer that an item has been produced
        }
        return NULL;
    }
    
    void *consumer(void *arg) {
        for (int i = 1; i <= 5; i++) {
            sem_wait(&sem_cons); // Wait until the producer has produced an item
            printf("Consumed: %d\n", shared_var);
            sem_post(&sem_prod); // Signal the producer that the item has been consumed
        }
        return NULL;
    }
    
    int main() {
        pthread_t prod_tid, cons_tid;
    
        sem_init(&sem_prod, 0, 1); // Initialize the producer semaphore to 1
        sem_init(&sem_cons, 0, 0); // Initialize the consumer semaphore to 0
    
        pthread_create(&prod_tid, NULL, producer, NULL); // Create producer thread
        pthread_create(&cons_tid, NULL, consumer, NULL); // Create consumer thread
    
        pthread_join(prod_tid, NULL); // Wait for producer thread to finish
        pthread_join(cons_tid, NULL); // Wait for consumer thread to finish
    
        sem_destroy(&sem_prod); // Destroy the producer semaphore
        sem_destroy(&sem_cons); // Destroy the consumer semaphore
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    执行结果如下:

    majn@tiger:~/C_Project/prod_cons$ ./prod_cons 
    Produced: 1
    Consumed: 1
    Produced: 2
    Consumed: 2
    Produced: 3
    Consumed: 3
    Produced: 4
    Consumed: 4
    Produced: 5
    Consumed: 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这个例子中:

    • sem_prodsem_cons 是无名信号量,用于生产者和消费者之间的同步。
    • sem_prod 初始化为1,允许生产者线程首先运行。
    • sem_cons 初始化为0,因为一开始没有可供消费的项目。
    • 生产者线程生成一个整数并将其存储在 shared_var 中,然后通过 sem_post(&sem_cons) 信号消费者可以消费。
    • 消费者线程等待 sem_cons 信号量,读取 shared_var 中的整数,然后通过 sem_post(&sem_prod) 信号生产者可以生产。
    • 主函数等待这两个线程完成,并最后销毁信号量。

    】在上述示例中,pthread_t tid; 是一个变量,用于存储新创建的线程的线程ID。此变量在声明时确实没有初始化,但这不是问题,因为它将在 pthread_create 调用中被明确地设置。

    当调用 pthread_create 时,其第一个参数是一个指向 pthread_t 类型变量的指针。这个变量用于存储新创建的线程的ID。pthread_create 在成功地创建线程后将会设置它。


    pthread_create & pthread_join

    pthread_createpthread_join 是 POSIX 线程(通常称为 pthreads)编程中的两个基本函数。它们分别用于创建新的线程和等待线程完成。以下是对这两个函数的详细说明:

    1. pthread_create

    此函数用于创建一个新的线程。

    原型:

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    
    • 1

    参数:

    • thread: 指向 pthread_t 类型变量的指针,该变量用于存储新线程的ID。
    • attr: 指向 pthread_attr_t 的指针,用于设置线程属性。如果设置为 NULL,则使用默认属性。
    • start_routine: 新线程启动时要执行的函数。这个函数必须返回 void * 并接受一个 void * 参数。
    • arg: 传递给 start_routine 的参数。

    返回值:

    • 0: 成功。
    • 错误号: 失败。

    2. pthread_join

    此函数用于等待线程完成。调用线程会被阻塞,直到指定的线程完成为止。

    原型:

    int pthread_join(pthread_t thread, void **retval);
    
    • 1

    参数:

    • thread: 要等待的线程的ID。
    • retval: 一个指向 void * 的指针,用于捕获线程的返回值。如果不关心返回值,可以设置为 NULL

    返回值:

    • 0: 成功。
    • 错误号: 失败。

    示例

    以下是一个简单的示例,说明如何使用 pthread_createpthread_join

    #include 
    #include 
    
    void *print_hello(void *data) {
        char *message = (char *)data;
        printf("%s\n", message);
        return NULL;
    }
    
    int main() {
        pthread_t tid;
        char *message = "Hello from the thread!";
    
        // 创建一个新线程
        if (pthread_create(&tid, NULL, print_hello, message) != 0) {
            printf("Error creating thread.\n");
            return 1;
        }
    
        // 等待线程完成
        pthread_join(tid, NULL);
    
        printf("Thread has finished execution.\n");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在上述示例中,主函数创建了一个新的线程,该线程调用 print_hello 函数并传递一个消息作为参数。然后,主函数使用 pthread_join 等待线程完成执行。

    majn@tiger:~/C_Project/pthread_project$ ./pthread_demo 
    Hello from the thread!
    Thread has finished execution.
    
    • 1
    • 2
    • 3
  • 相关阅读:
    刷题记录:NC17871CSL分苹果
    攻防世界-adworld-fileinclude
    YOLOX加强特征提取网络Panet分析
    硅麦驱动开发及调试(pdm>>I2S>>pcm)
    C++实现对Json数据的友好处理
    CSP 202109-1 数值推导
    经典算法之希尔排序
    Python实现print输出至日志文件
    创新战略|工业企业如何应对颠覆式变革带来的挑战?
    pojo层、dao层、service层、controller层的作用
  • 原文地址:https://blog.csdn.net/weixin_43844521/article/details/133240795