https://www.cnblogs.com/52php/p/5851570.html https://blog.csdn.net/wing_7/article/details/79517768

一、什么是信号量

为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域

  • 临界区域是指执行数据更新的代码需要独占式地执行。
  • 信号量可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。


信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。

  • 二进制信号量:信号量只能取0和1。控制单个资源,初始值为1。
  • 通用信号量:可以取多个正整数的信号量

PV操作是什么?

二、信号量的工作原理

为了获得共享资源,进程需要执行下列操作:

  • 1.测试控制该资源的信号量
  • 2.若此信号量的值为正,则进程可以使用该资源。在这种情况下,进程会将信号量值减1,表示它使用了一个资源单位。
  • 3.否则,若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0。进程被唤醒后,它返回至步骤1。

当进程不再使用由一个信号量控制的共享内存时,该信号量值增1。如果有进程正则休眠等待此信号量,则唤醒它们。

缺点:

  • 必须有公共内存,不能用于分布式操作系统,这是它最大的弱点

    三、Linux的信号量机制

    Linux提供了一组精心设计的信号量接口来对信号进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。

    3.1 semget()函数

    创建一个新信号量或取得一个已有信号量

函数原型:

  1. int semget(key_t key, int num_sems, int sem_flags);

参数:

  1. 整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
  2. 需要的信号量数目,它的值几乎总是1
  3. 一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

返回值:

  • 成功:返回一个相应信号标识符(非零)
  • 失败:返回-1

    3.2 semop()函数

    改变信号量的值

函数原型:

  1. int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

参数:

  1. semget()返回的信号量标识符
  2. 指向存储信号操作结构的数组指针,见下面结构体 sembuf
  3. 信号操作结构的数量,恒大于或等于1
    1. struct sembuf{
    2. short sem_num; // 除非使用一组信号量, 否则它为0
    3. short sem_op; // 信号量在一次操作中需要改变的数据, 通常是两个数:
    4. // -1,即P(等待)操作
    5. // +1,即V(发送信号)操作
    6. short sem_flg; // 通常为SEM_UNDO, 使操作系统跟踪信号, 并在进程没有释放该信号量而终止时, 操作
    7. // 系统释放信号量
    8. };
    返回值:
  • 失败:返回-1

    3.3 semctl()函数

    用来直接控制信号量信息
    函数原型:

    1. int semctl(int sem_id, int sem_num, int command, /*union semun arg*/);

    参数:

  • 由semget()返回的信号量标识符

  • 操作信号在信号集中的编号,第一个信号的编号是0
  • 通常使用如下两个值:
    • SETVAL:用来把信号量初始化为一个已知的值。p这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
    • IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
  • 参数arg代表一个semun的实例

    1. union semun {
    2. int val;
    3. struct semid_ds *buf;
    4. unsigned short *arry;
    5. // ...
    6. };

    返回值:

  • 如果成功,则为一个正数

  • 失败:返回-1

    四、示例代码

    ```cpp // demo.cpp

    include

    include

    include

    using namespace std;

// 信号量标识符 static int sem_id = 0;

// 初始化信号量, 成功返回正数, 失败返回-1 static int set_semvalue(); // 删除信号量 static int del_semvalue();

// 等待, 成功返回正数, 失败返回-1 static int semaphore_p(); // 释放, 成功返回正数, 失败返回-1 static int semaphore_v();

// argc长度为2, 参数2为true表示第一次启动,否则表示第二次或者第n次启动 int main(int argc, char* argv[]) { cout << argc << endl; if (argc != 2) { cout << “命令行参数错误” << endl; return -1; }

  1. string is_first_start = argv[1];
  2. string view_content = is_first_start == "true" ? "a-" : "b-";
  3. // 创建信号量
  4. sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
  5. // 程序第一次被调用,初始化信号量
  6. if (is_first_start == "true") {
  7. if (set_semvalue() == -1) {
  8. cout << "Failed to initialize semaphore." << endl;
  9. sleep(3);
  10. }
  11. }
  12. for (int i = 0; i < 10; i++) {
  13. // 进入临界区
  14. if (-1 == semaphore_p()) {
  15. exit(EXIT_FAILURE);
  16. }
  17. cout << view_content << i << "-1" << endl;
  18. // 清理缓冲区,然后休眠随机时间
  19. cout << flush;
  20. sleep(rand() % 3);
  21. // 离开临界区前再一次向屏幕输出数据
  22. cout << view_content << i << "-1" << endl;
  23. cout << flush;
  24. // 离开临界区,休眠随机时间后继续循环
  25. if (-1 == semaphore_v()) {
  26. exit(EXIT_FAILURE);
  27. }
  28. }
  29. sleep(10);
  30. cout << getpid() << "-finished" << endl;
  31. if (is_first_start == "true") {
  32. sleep(3);
  33. del_semvalue();
  34. }
  35. return 0;

}

union semun { int val; struct semid_ds buf; unsigned short arry; };

static int set_semvalue() { // 用于初始化信号量,在使用信号量前必须这样做 union semun sem_union; sem_union.val = 1; return semctl(sem_id, 0, SETVAL, sem_union); }

static int del_semvalue() { union semun sem_union; return semctl(sem_id, 0, IPC_RMID, sem_union); }

static int semaphore_p() { // 对信号量做减1操作,即等待P(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1; // P() sem_b.sem_flg = SEM_UNDO;

  1. return semop(sem_id, &sem_b, 1);

}

static int semaphore_v() { // 这是一个释放操作,它使信号量变为可用,即发送信号V(sv) struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1; // V() sem_b.sem_flg = SEM_UNDO;

  1. return semop(sem_id, &sem_b, 1);

}

  1. 编译
  2. ```cpp
  3. g++ demo.cpp -o demo -std=c++11
  4. ./demo true
  5. ./demo false

终端1

  1. a-0-1
  2. a-0-1
  3. a-1-1
  4. a-1-1
  5. a-2-1
  6. a-2-1
  7. a-3-1
  8. a-3-1
  9. a-4-1
  10. a-4-1
  11. a-5-1
  12. a-5-1
  13. a-6-1
  14. a-6-1
  15. a-7-1
  16. a-7-1
  17. a-8-1
  18. a-8-1
  19. a-9-1
  20. a-9-1
  21. 63908-finished

终端2

  1. b-0-1
  2. b-0-1
  3. b-1-1
  4. b-1-1
  5. b-2-1
  6. b-2-1
  7. b-3-1
  8. b-3-1
  9. b-4-1
  10. b-4-1
  11. b-5-1
  12. b-5-1
  13. b-6-1
  14. b-6-1
  15. b-7-1
  16. b-7-1
  17. b-8-1
  18. b-8-1
  19. b-9-1
  20. b-9-1
  21. 63910-finished

五、封装类

  1. #include <iostream>
  2. #include <sys/sem.h>
  3. #include <unistd.h>
  4. using namespace std;
  5. /* 信号量 的 封装类
  6. * 1. 构造函数中会创建信号量及控制信号量信息
  7. * 2. semaphore_p、semaphore_v 相当于PV操作(等待和发送)
  8. * 3. 析构函数会删除创建的信号量
  9. **/
  10. class Semaphore {
  11. public:
  12. Semaphore(key_t val)
  13. {
  14. key_value = val;
  15. }
  16. ~Semaphore()
  17. {
  18. delete_semaphore();
  19. }
  20. // 1.创建信号量 参数为键值
  21. void create_semaphore()
  22. {
  23. m_semId = semget((key_t)key_value, 1, 0666 | IPC_CREAT);
  24. }
  25. // 2.控制信号量信息
  26. int set_sem_val()
  27. {
  28. union semun sem_union;
  29. sem_union.val = 1;
  30. return semctl(m_semId, 0, SETVAL, sem_union) == -1;
  31. }
  32. // 3.信号量的值减1
  33. int semaphore_p()
  34. {
  35. struct sembuf sem_b;
  36. sem_b.sem_num = 0;
  37. sem_b.sem_op = -1; //P()
  38. sem_b.sem_flg = SEM_UNDO;
  39. return semop(m_semId, &sem_b, 1) == -1;
  40. }
  41. // 4.信号量的值加1
  42. int semaphore_v()
  43. {
  44. struct sembuf sem_b;
  45. sem_b.sem_num = 0;
  46. sem_b.sem_op = 1;
  47. sem_b.sem_flg = SEM_UNDO;
  48. return semop(m_semId, &sem_b, 1) == -1;
  49. }
  50. // 5. 删除一个已经无需继续使用的信号量标识符
  51. int delete_semaphore()
  52. {
  53. union semun sem_union;
  54. return semctl(m_semId, 0, IPC_RMID, sem_union) == -1;
  55. }
  56. private:
  57. union semun {
  58. int val;
  59. struct semid_ds* buf;
  60. unsigned short* arry;
  61. };
  62. private:
  63. // 信号量标识符
  64. int m_semId;
  65. key_t key_value;
  66. };

使用:

// argc长度为2, 参数2为true表示第一次启动,否则表示第二次或者第n次启动
int main(int argc, char* argv[])
{
    cout << argc << endl;
    if (argc != 2) {
        cout << "命令行参数错误" << endl;
        return -1;
    }

    string is_first_start = argv[1];
    string view_content = is_first_start == "true" ? "a-" : "b-";
    // 创建信号量
    Semaphore sem(1111);
    sem.create_semaphore();
    // 程序第一次被调用,初始化信号量
    if (is_first_start == "true") {
        if (sem.set_sem_val() == -1) {
            cout << "Failed to initialize semaphore." << endl;
            sleep(3);
        }
    }

    for (int i = 0; i < 10; i++) {
        // 进入临界区
        if (sem.semaphore_p()) {
            exit(EXIT_FAILURE);
        }

        cout << view_content << i << "-1" << endl;
        // 清理缓冲区,然后休眠随机时间
        cout << flush;
        sleep(rand() % 3);

        // 离开临界区前再一次向屏幕输出数据
        cout << view_content << i << "-1" << endl;
        cout << flush;

        // 离开临界区,休眠随机时间后继续循环
        if (sem.semaphore_v()) {
            exit(EXIT_FAILURE);
        }
    }
    sleep(10);
    cout << getpid() << "-finished" << endl;
    if (is_first_start == "true") {
        sleep(3);
        sem.delete_semaphore();
    }
    return 0;
}

编译结果和“示例代码”中的一样。

问题,两个进程 如何判断谁是先启动的?