简 述: 上一篇中讲解了“条件变量 + 互斥量”的组合使用,演示了 “生产者-消费者”模型。本篇讲解 互斥量的升级版:信号量(信号灯) 的理解和使用。互斥量与信号量的关系,可以简单理解为 c 和 c++ 的关系。信号量的使用的步骤,也是和前面的互斥量很像,不过这次的头文件改为了 #include <semaphore.h>:

  • sem_t sem; //定义变量
  • sem_wait(); //加锁
  • …其他代码
  • sem_post(); //解锁
  • sem_destroy(); //销毁

说明:

本例子是在 Linux 下面运行成功的,编译时候,时候需要加参数 -pthread

若是想要在 Mac 运行改程序,需要改写替换部分函数(mac 不支持其中的部分函数)

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


编程环境:

  💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

  💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


信号量(信号灯):

简单理解为里面具有多个互斥量的集合。是加强版的互斥锁。

其他:

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

之间的一些解释:

sem 变量和 函数 sem_init 中的 sem 参数是同一个;在函数里面,第一个参数 sem 实际是和第三个参数 value 关联的;表面上对 sem 进行修改,实际上是修改关联的 value 的值,对其进行 ++ 或 – 操作。(嗯嗯,原理就这么理解就行)。


使用步骤:

  1. sem_t sem;


  2. 初始化信号量

    int sem_init(sem_t *sem, int pshared, unsigned int value);
    • 参数:
      • pshared:
        • 0 - 线程同步
        • 1 - 进程同步
      • value:
        • 最多有几个线程操作共享数据

  3. 加锁:

    //调用一次相当于对 sem 做了一次 -- 操作
    int sem_wait(sem_t *sem);  //加锁; => 如果 sem 值为 0,线程会阻塞
    
    int sem_trywait(sem_t *sem);  //尝试加锁; => sem == 0,加锁失败,不阻塞,直接返回
    
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);  //限时尝试加锁

  4. 解锁:

    int sem_post(sem_t *sem);  //相当于对于 sem 做了 ++ 操作

  5. 销毁信号量

    int sem_destroy(sem_t *sem);

“生产者-消费者”例子:

将上一篇的例子改一改,直接使用信号量来写这个例子“生产者-消费者”。本篇文章例子如下:


理论模型:


代码分析:

多看一下理论模型的那个伪代码的流程图,多体会揣摩其中的箭头的流向,且一开始这设置为消费者为阻塞,当生产者生产之后,为其解锁。


代码实现:

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

sem_t semProducer;
sem_t semCustomer;

typedef struct node  //节点结构
{
    int data;
    struct node* next;
} Node;

Node* g_head =  nullptr;  //永远指向链表头部的指针

void* funProducer(void* arg);  //生产者--添加一个头结点
void* funCustomer(void* arg);  //消费者--删除一个头结点

int main(int argc, char *argv[])
{
    pthread_t p1;
    pthread_t p2;

    sem_init(&semProducer, 0, 4);  //初始化生产者线程信号量, (赋予 4 个,对比下一行,让生产者先运行)
    sem_init(&semCustomer, 0, 0);  //初始化消费者线程信号量, (赋予 0 个, 一开始就让消费者处于阻塞状态)

    pthread_create(&p1, nullptr, funProducer, nullptr);  //创建生产者线程
    pthread_create(&p2, nullptr, funCustomer, nullptr);  //创建消费者线程

    pthread_join(p1, nullptr);  //阻塞回收子线程
    pthread_join(p2, nullptr);

    sem_destroy(&semProducer);  //销毁生产者信号量
    sem_destroy(&semCustomer);  //销毁消费者信号量

    return 0;
}

void* funProducer(void* arg)
{
    while (true) {
        sem_wait(&semProducer);  //semProducer--,  == 0, 则阻塞
        Node* pNew = new Node();
        pNew->data = rand() % 1000; 
        pNew->next = g_head;
        g_head = pNew;
        printf("-----funProducer(生产者): %lu, %d\n", pthread_self(), pNew->data);
        sem_post(&semCustomer); // semCustomer++

        sleep(rand() % 3);  //随机休息 0~2 s
    }
    
    return nullptr;
}

void* funCustomer(void* arg)
{
    while (true) {
        sem_wait(&semCustomer);  //semCustomer--,  == 0, 则阻塞
        Node* pDel = g_head;
        g_head = g_head->next;
        printf("-----funCustomer(消费者): %lu, %d\n", pthread_self(), pDel->data);
        delete pDel;
        sem_post(&semProducer); // semProducer++
    }

    return nullptr;
}

运行结果:

这个例子是在 uos20 上面运行的成功的;编译的时候,记得加上参数 -pthread


Mac 下对 sem_init()/sem_destory() 不支持:

注意:

MacOS 不支持 sem_init()sem_destroy();这个例子若是想在 mac 下编译通过,需要自行修改替换相关的函数。

  • sem_init(&sem, 0, 1) 改成 sem_open("sem", O_CREAT|O_EXCL, S_IRWXU, 0)
  • sem_destory(&sem) 改成 sem_unlink("sem");
  • 且支持 pthread_mutex_init(&mutex, NULL) 却不支持 pthread_mutex_destory(&mutex)

Mac 的该文件在 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/semaphore.h 路径,可以查看到该头文件的源码,附上详细 mac 下该库源码:

#ifndef _SYS_SEMAPHORE_H_
#define _SYS_SEMAPHORE_H_

typedef int sem_t;

/* this should go in limits.h> */
#define SEM_VALUE_MAX 32767
#define SEM_FAILED ((sem_t *)-1)

#include <sys/cdefs.h>

__BEGIN_DECLS
int sem_close(sem_t *);
int sem_destroy(sem_t *) __deprecated;
int sem_getvalue(sem_t * __restrict, int * __restrict) __deprecated;
int sem_init(sem_t *, int, unsigned int) __deprecated;
sem_t * sem_open(const char *, int, ...);
int sem_post(sem_t *);
int sem_trywait(sem_t *);
int sem_unlink(const char *);
int sem_wait(sem_t *) __DARWIN_ALIAS_C(sem_wait);
__END_DECLS


#endif  /* _SYS_SEMAPHORE_H_ */

下载地址:

21_semaphore

欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。