Linux 信号量 生产者消费者小例题

  菜鸟偶遇信号量,擦出火花(只有不熟才会有火花)。于是上网搜资料和看《Unix环境高级编程》实现了几个小例题,高手请勿喷!这几位写得非常好啊:

题目来源: http://www.it165.net/os/html/201312/7039.html

信号量及其用法:http://www.cnblogs.com/hjslovewcl/archive/2011/03/03/2314341.html

Mutex与Semaphore区别著名的厕所理论:http://koti.mbnet.fi/niclasw/MutexSemaphore.html

   哎呀,暴露了!我不是故意偷窥别人的……




一:一个生产者、一个消费者、一个资源情况

  这种情况情况可以只用一个信号量,要生成或要消费只用尝试获取这个信号量,这里用了两个:full=1和empty=0,两个只为了和后面一致,1、0是赋初值。生产者和消费者情况如下:

//生产者:
P(empty)
    生成资源并放进资源处
V(full)


//消费者:
P(full)
    消费资源
V(empty)

  若生产者最先开始生产资源,P(empty),full和empty都成了0,此时若消费者想要消费,则P(full)时,full为0则睡眠等待,等生产者生结束就把full加1,看到消费者可怜地睡着了就唤醒它,然后消费者把full减1自己快活去了。

  消费者消费过程中生产者若要生了,则会因为empty为0而休眠,等消费者结束就把empty加1,然后生产者开始生产。

  上面的好理解,下面上代码:

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

#include <x86_64-linux-gnu/sys/types.h>
#include <x86_64-linux-gnu/sys/ipc.h>
#include <x86_64-linux-gnu/sys/sem.h>

int semInite(int semId, int value);
int semDelete(int semId);
int semP(int semId);
int semV(int semId);

//declare a union to be used
union semun 
{
    int val;                        /* value for SETVAL */ 
    struct semid_ds *buf;                /* buffer for IPC_STAT, IPC_SET */ 
    unsigned short int *array;         /* array for GETALL, SETALL */ 
    struct seminfo *__buf;                /* buffer for IPC_INFO */ 
};


//semaphore declare
static int semFullId;
static int semEmptyId;
static int source = 0;        //source definition    


//new thread as a consumer
void* child_thread(void* arg)
{
    int ttt = 1;
        
    while(1)
    {
        sleep(rand() % 19);
        printf("child No.%d times wants to consume...
", ttt);
        
        semP(semFullId);    //
        printf("child No.%d times start consuming. source = %d
", ttt, source);
        source = 0;
        printf("child No.%d times end consuming. source = %d

", ttt++, source);
        semV(semEmptyId);    //
    }
    
    return (void*)0;
}

int main(void)
{    
    //create semaphore
    semFullId = semget((key_t)1235, 1, 0666 | IPC_CREAT);
    semEmptyId = semget((key_t)1236, 1, 0666 | IPC_CREAT);
    semInite(semFullId, 0);
    semInite(semEmptyId, 1);
    
    pthread_t pid;
    pthread_create(&pid, NULL, child_thread, NULL);
    
    int tt = 1;    
    while(1)
    {
        sleep(rand() % 18);
        printf("parent No.%d times wants to produce...
", tt);
        
        semP(semEmptyId);    //
        printf("parent No.%d times start producing. source = %d
", tt, source);
        source = rand() % 100;
        printf("parent No.%d times end producing. source = %d
", tt++, source);
        semV(semFullId);    //
    }
    
    semDelete(semFullId);
    semDelete(semEmptyId);
    return 0;
}


//set semaphore as default value
int semInite(int semId, int value)
{
    union semun semUnion;
    semUnion.val = value;    //set default semaphore
    return semctl(semId, 0, SETVAL, semUnion);
}

//delete semaphore
int semDelete(int semId)
{
    union semun semUnion;
    return semctl(semId, 0, IPC_RMID, semUnion);
}

//semaphore P operation
int semP(int semId)
{
    struct sembuf semBuf;
    semBuf.sem_num = 0;        //indicate it is not semaphore array
    semBuf.sem_op = -1;        //subtract one
    semBuf.sem_flg = SEM_UNDO;
    
    return semop(semId, &semBuf, 1);    //return value
}

//semaphore V operation
int semV(int semId)
{
    struct sembuf semBuf;
    semBuf.sem_num = 0;        //indicate it is not semaphore array
    semBuf.sem_op = 1;        //subtract one
    semBuf.sem_flg = SEM_UNDO;
    
    return semop(semId, &semBuf, 1);    //return value
}
111

两个信号量其实应该用信号量集的,因为它本来就是针对集合的,但是由于刚入门,为了易懂,就用两个。两个线程,创建的新线程当做消费者了。其中unix的几个信号量的函数看了半天,有点复杂,简单不准确来讲:

//获得一个信号量啦,第二个参数是想要创建的信号量个数,
//因为unix操作的是信号量集合,设为1不就一个信号量了嘛
//其他参数我不管了
int semget(key_t key, int num_sems, int sem_flags);

//信号量集合的操作,这个可以用来实现P、V的 +1 -1 的功能
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

//信号量集合的控制,如初始化删除等
int semctl(int sem_id, int sem_num, int command, ...);

运行:




二:一个生产者、一个消费者、N个资源情况

  这里资源用是一个数组代替了。其实本质上和上面类似,每次只让生产者或消费者中的一个进入,进入后放到哪个地方或从哪个地方取就得用一个标志来说明了,其实也可以为每一资源加上信号量的。

  这里在生产者和消费者那里都设置了一个static的变量当做游标,指示下个资源放到哪个位置和下次从哪取资源。staitic变量用在这里很合适,因为只会初始化一次。

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

#include <x86_64-linux-gnu/sys/types.h>
#include <x86_64-linux-gnu/sys/ipc.h>
#include <x86_64-linux-gnu/sys/sem.h>

#define N 5

int semInite(int semId, int value);
int semDelete(int semId);
int semP(int semId);
int semV(int semId);

//declare a union to be used
union semun 
{
    int val;                        /* value for SETVAL */ 
    struct semid_ds *buf;                /* buffer for IPC_STAT, IPC_SET */ 
    unsigned short int *array;         /* array for GETALL, SETALL */ 
    struct seminfo *__buf;                /* buffer for IPC_INFO */ 
};


//semaphore declare
static int semFullId;
static int semEmptyId;
static int srcArr[N];        //source definition    


//new thread as a consumer
void* child_thread(void* arg)
{
    int ttt = 1;
        
    while(1)
    {
        static int pToGet = 0;    //get source from the position
        sleep(rand() % 19);
        printf("child No.%d times wants to consume(get from index %d)...
", ttt, pToGet);
        
        semP(semFullId);    //
        printf("child No.%d times start consuming.(get from index %d, data is %d)
", ttt, pToGet, srcArr[pToGet]);
        srcArr[pToGet] = 0;
        printf("child No.%d times end consuming. (get from index %d)

", ttt++, pToGet);
        pToGet = (pToGet + 1) % N;
        semV(semEmptyId);    //
    }
    
    return (void*)0;
}

int main(void)
{    
    //create semaphore
    semFullId = semget((key_t)1235, 1, 0666 | IPC_CREAT);
    semEmptyId = semget((key_t)1236, 1, 0666 | IPC_CREAT);
    semInite(semFullId, 0);
    semInite(semEmptyId, N);    //N source
    
    pthread_t pid;
    pthread_create(&pid, NULL, child_thread, NULL);
    
    int tt = 1;    
    while(1)
    {
        static int pToPut = 0;        //next position where source to be filled in
        sleep(rand() % 18);
        printf("parent No.%d times wants to produce(put in %d index)...
", tt, pToPut);
        
        semP(semEmptyId);    //
        printf("parent No.%d times start producing.(put in %d index, original data is %d)
", tt, pToPut, srcArr[pToPut]);
        int temp = rand() % 100;
        srcArr[pToPut] = temp;
        printf("parent No.%d times end producing.(put in %d index, now data is %d)
", tt++, pToPut, srcArr[pToPut]);
        pToPut = (pToPut + 1) % N;
        semV(semFullId);    //
    }
    
    semDelete(semFullId);
    semDelete(semEmptyId);
    return 0;
}


//set semaphore as default value
int semInite(int semId, int value)
{
    union semun semUnion;
    semUnion.val = value;    //set default semaphore
    return semctl(semId, 0, SETVAL, semUnion);
}

//delete semaphore
int semDelete(int semId)
{
    union semun semUnion;
    return semctl(semId, 0, IPC_RMID, semUnion);
}

//semaphore P operation
int semP(int semId)
{
    struct sembuf semBuf;
    semBuf.sem_num = 0;        //indicate it is not semaphore array
    semBuf.sem_op = -1;        //subtract one
    semBuf.sem_flg = SEM_UNDO;
    
    return semop(semId, &semBuf, 1);    //return value
}

//semaphore V operation
int semV(int semId)
{
    struct sembuf semBuf;
    semBuf.sem_num = 0;        //indicate it is not semaphore array
    semBuf.sem_op = 1;        //subtract one
    semBuf.sem_flg = SEM_UNDO;
    
    return semop(semId, &semBuf, 1);    //return value
}
222

运行结果:




三:N个生产者,N个消费者,N个资源

   这种情况不仅生产者和消费者之间要通过上述的方式协调使用资源,而且生产者内部和消费者内部也要协调。定义四个信号量:

empty——表示缓冲区是否为空,初值为n。
full——表示缓冲区中是否为满,初值为0。
mutex1——生产者之间的互斥信号量,初值为1。
mutex2——消费者之间的互斥信号量,初值为1。

//生产者进程
P(mutex1)
    P(empty)
        生产数据并放进特定位置
    V(full)
V(mutex1)

//消费者进程
P(mutex2)
    P(full)
        消费数据
    V(empty)
V(mutex2)

其实上面生产者或者消费者获取互斥量或信号量的顺序可以颠倒的,不会产生死锁。

  当然这个问题可以用其他更好的方式解决,我还得继续学习。

原文地址:https://www.cnblogs.com/jiayith/p/3854312.html