1 信号量作用

信号量就是用来解决进程间的同步与互斥问题的一种进程间通信机制。

2 同步、异步、互斥理解

  • 同步:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。
  • 异步:异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
  • 互斥:多个线程或进程分别独立访问共享数据。

3 信号和信号量的区别

  • 信号:(signal)是一种处理异步事件的方式。信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程外,还可以发送信号给进程本身。
  • 信号量:(Semaphore)进程间通信处理同步互斥的机制。是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。

4 信号量类别

  1. POSIX提供信号量:
  • 有名信号量:
  • 无名信号量:
    区别:
  • 有名信号量一般是用在进程间同步,无名信号量一般用在线程间同步。
  • 使用区别:
  1. System V 信号量:
  • 信号量只能进行等待和发送信号两种操作,即P(sv)和V(sv)
  • P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程并将其PCB放入队列中
  • V(sv):如果等待队列中有其他进程因等待sv而被挂起,就让一个恢复运行,如果没有进程因等待sv而挂起,就给它加1
    所以一个信号量不仅包含临界资源的数目,还必须维护一个队列。

5 信号量使用

5.1 无名信号量

5.1.1 接口介绍

  1. 创建信号
int sem_init (sem_t* sem, int pshared,unsigned int value);
  • sem :信号量的地址
  • pshared :等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享
  • value :信号量初始值
  1. 操作信号
int sem_post (sem_t* sem);// 信号量加1
int sem_wait (sem_t* sem);//信号量减1,不够减即阻塞
int sem_trywait (sem_t* sem);// 信号量减1,不够减即返回-1,errno为EAGAIN
int sem_timedwait (sem_t* sem, const struct timespec* abs_timeout);
// 信号量减1,不够减即阻塞,直到abs_timeout超时返回-1,errno为ETIMEDOUT
struct timespec {
time_t tv_sec;  // Seconds
long   tv_nsec; // Nanoseconds [0 - 999999999]
}
int sem_getvalue(sem_t *sem, int *sval);
//把 sem 指向的信号量当前值放置在 sval 指向的整数上
  1. 销毁信号
int sem_destroy (sem_t* sem);

5.1.2 使用示例

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <thread>
#include <errno.h>
#define BUFF_SIZE (20)
#define MAX_THREADS (10)

struct SHARED{
    int buff[BUFF_SIZE] = {0};
    sem_t mutex, nempty, nstored;

}shared;

void produce (int i)
{
    int k = 0;
    while(k++ < 20)
    {
        sem_wait(&shared.nempty);
        sem_wait(&shared.mutex);
        for(int j = 0; j < BUFF_SIZE; j++)
        {
            if(shared.buff[j] == 0)
            {
                shared.buff[j] = i;
                printf("j,i = [%d, %d]\n", j, i);
                break;
            }
        }

        sem_post(&shared.mutex);
        sem_post(&shared.nstored);
    }

}

void consume( int i)
{
    int k = 0;
    while(k++ < 20)
    {
        sem_wait(&shared.nstored);
        sem_wait(&shared.mutex);
        for(int j = 0; j < BUFF_SIZE; j++)
        {
            if(shared.buff[j] != 0)
            {
                printf("buff[%5d] = [%d]\n", j, shared.buff[j]);
                shared.buff[j] = 0;
                break;
            }
        }
        sem_post(&shared.mutex);
        sem_post(&shared.nempty);
    }

}

int main()
{
    std::thread thread_produce[MAX_THREADS], thread_consume[MAX_THREADS];

    sem_init(&shared.mutex, 0, 1);
    sem_init(&shared.nempty, 0, BUFF_SIZE);
    sem_init(&shared.nstored, 0, 0);
    for(int i = 0; i < MAX_THREADS; i++)
    {
        thread_produce[i] = std::thread(produce, i + 1);
    }

    for(int i = 0; i < MAX_THREADS; i++)
    {
        thread_consume[i] = std::thread(consume, i + 1);
    }

    for(int i = 0; i < MAX_THREADS; i++)
    {
        thread_produce[i].join();
    }

    for(int i = 0; i < MAX_THREADS; i++)
    {
        thread_consume[i].join();
    }

    sem_destroy(&shared.mutex);
    sem_destroy(&shared.nempty);
    sem_destroy(&shared.nstored);
    
    return 0;
}
g++ test3.cpp -lpthread

注意task写法:

#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <list>
#include <semaphore.h>
#include <iostream>

class Task
{
public:
    Task(int taskID)
    {
        this->taskID = taskID;
    }

    void doTask()
    {
        std::cout<< "handle a task, taskID:" << taskID << ", threadID:" << pthread_self() << std::endl;
    }
private:
    int taskID;
};

pthread_mutex_t mymutex;
std::list<Task*> tasks;
sem_t mysemaphore;

void* consumer_thread(void* param)
{
    Task *pTask = NULL;
    while(true) {
        if (sem_wait(&mysemaphore) != 0) {
            continue;
        }

        if(tasks.empty()) {
            continue;
        }

        pthread_mutex_lock(&mymutex);
        pTask = tasks.front();
        tasks.pop_front();
        pthread_mutex_unlock(&mymutex);


        pTask->doTask();
        delete pTask;
    }

    return NULL;
}

void* producer_thread(void* param)
{
    int taskID = 0;
    Task* pTask = NULL;

    while(true) {
        pTask = new Task(taskID);

        pthread_mutex_lock(&mymutex);
        tasks.push_back(pTask);
        std::cout << "produce a task, taskID:" << taskID << ", threadID:" << pthread_self() << std::endl;
        pthread_mutex_unlock(&mymutex);

        sem_post(&mysemaphore);

        taskID++;
        sleep(1);
    }

    return NULL;

}

int main()
{
    pthread_mutex_init(&mymutex, NULL);
    sem_init(&mysemaphore, 0, 0);

    pthread_t consumerThreadID[5];
    for(int i = 0; i < 5; i++) {
        pthread_create(&consumerThreadID[i], NULL, consumer_thread, NULL);
    }

    pthread_t producerThread;
    pthread_create(&producerThread, NULL, producer_thread, NULL);

    pthread_join(producerThread, NULL);

    for(int i = 0; i < 5; i++) {
        pthread_join(consumerThreadID[i], NULL);
    }

    sem_destroy(&mysemaphore);
    pthread_mutex_destroy(&mymutex);
    return 0;
}

5.2 有名信号量

  1. 创建信号
sem_t *sem_open(const char *name, int oflag);//打开一个有名信号量,此时有名信号量是已经存在了的。
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);//创建有名信号量

返回值:若成功,返回信号量的地址;若出错,返回SEM_FAILED
参数:

  • name:信号量文件名。
  • flags:sem_open() 函数的行为标志。
  • mode:文件权限,可用八进制表示,如0777.
  • value:信号量初始值。
  1. 关闭信号
int sem_close(sem_t *sem);//关闭有名信号量

5.3 System V 信号量

5.3.1 使用步骤

  1. 创建信号量或获得在系统中已存在的信号量,此时需要调用 semget() 函数。不同进程通过使用同一个信号量键值来获得同一个信号量。
  2. 初始化信号量,此时使用 semctl() 函数的SETVAL操作。当使用二维信号量时,通常将信号量初始化为1。
  3. 进行信号量的PV操作,此时,调用 semop()函数。这一步是实现进程间的同步和互斥的核心工作部分。
  4. 如果不需要信号量,则从系统中删除它,此时使用semctl()函数的 IPC_RMID操作。需要注意的是,在程序中不应该出现对已经被删除的信号量的操作。

5.3.2 接口介绍

5.3.2.1 信号量的创建

ios 信号量控制并发数量 信号量进程_#include

5.3.2.2 初始化信号量

ios 信号量控制并发数量 信号量进程_信号量_02

5.3.2.3信号量操作

ios 信号量控制并发数量 信号量进程_i++_03

5.3.2.4 信号量的删除

IPC_RMID :从系统中删除该信号量集合