菜鸟偶遇信号量,擦出火花(只有不熟才会有火花)。于是上网搜资料和看《Unix环境高级编程》实现了几个小例题,高手请勿喷!这几位写得非常好啊:
题目来源:
信号量及其用法:
Mutex与Semaphore区别著名的厕所理论:
哎呀,暴露了!我不是故意偷窥别人的……
一:一个生产者、一个消费者、一个资源情况
这种情况情况可以只用一个信号量,要生成或要消费只用尝试获取这个信号量,这里用了两个: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#include #include #include #include #include #include int semInite(int semId, int value);int semDelete(int semId);int semP(int semId);int semV(int semId);//declare a union to be usedunion 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 declarestatic int semFullId;static int semEmptyId;static int source = 0; //source definition //new thread as a consumervoid* child_thread(void* arg){ int ttt = 1; while(1) { sleep(rand() % 19); printf("child No.%d times wants to consume...\n", ttt); semP(semFullId); // printf("child No.%d times start consuming. source = %d\n", ttt, source); source = 0; printf("child No.%d times end consuming. source = %d\n\n", 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...\n", tt); semP(semEmptyId); // printf("parent No.%d times start producing. source = %d\n", tt, source); source = rand() % 100; printf("parent No.%d times end producing. source = %d\n", tt++, source); semV(semFullId); // } semDelete(semFullId); semDelete(semEmptyId); return 0;}//set semaphore as default valueint semInite(int semId, int value){ union semun semUnion; semUnion.val = value; //set default semaphore return semctl(semId, 0, SETVAL, semUnion);}//delete semaphoreint semDelete(int semId){ union semun semUnion; return semctl(semId, 0, IPC_RMID, semUnion);}//semaphore P operationint 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 operationint 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}
两个信号量其实应该用信号量集的,因为它本来就是针对集合的,但是由于刚入门,为了易懂,就用两个。两个线程,创建的新线程当做消费者了。其中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#include #include #include #include #include #include #define N 5int semInite(int semId, int value);int semDelete(int semId);int semP(int semId);int semV(int semId);//declare a union to be usedunion 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 declarestatic int semFullId;static int semEmptyId;static int srcArr[N]; //source definition //new thread as a consumervoid* 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)...\n", ttt, pToGet); semP(semFullId); // printf("child No.%d times start consuming.(get from index %d, data is %d)\n", ttt, pToGet, srcArr[pToGet]); srcArr[pToGet] = 0; printf("child No.%d times end consuming. (get from index %d)\n\n", 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)...\n", tt, pToPut); semP(semEmptyId); // printf("parent No.%d times start producing.(put in %d index, original data is %d)\n", 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)\n", tt++, pToPut, srcArr[pToPut]); pToPut = (pToPut + 1) % N; semV(semFullId); // } semDelete(semFullId); semDelete(semEmptyId); return 0;}//set semaphore as default valueint semInite(int semId, int value){ union semun semUnion; semUnion.val = value; //set default semaphore return semctl(semId, 0, SETVAL, semUnion);}//delete semaphoreint semDelete(int semId){ union semun semUnion; return semctl(semId, 0, IPC_RMID, semUnion);}//semaphore P operationint 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 operationint 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}
运行结果:
三: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)
其实上面生产者或者消费者获取互斥量或信号量的顺序可以颠倒的,不会产生死锁。
当然这个问题可以用其他更好的方式解决,我还得继续学习。