信号量(与信号没有任何关系)
【进化版互斥锁 (1–>N)】
由于互斥锁的粒度比较大,如果我么希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行变化了串行执行。与直接使用单进程无异。
【信号量:是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高高线程并发】
主要应用函数
1. sem_init 初始化信号量
2. sem_destroy 销魂信号量
3. sem_wait 申请信号量
4. sem_trywait
5. sem_timedwait
6. sem_post 释放信号量
【以上6个函数返回值都是:成功:0;失败:-1,同时设置errno。(注意,它们没有pthread前缀,意味着不仅仅能够在线程中使用)】
sem_init 函数
【初始化信号量】
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem_destroy 函数
【销毁信号量】
int sem_destroy(sem_t *sem);
sem_wait 函数
【申请信号量】
int sem_wait(sem_t *sem);
1. 若信号量大于0,则信号量--
2. 若信号量等于0,则造成线程阻塞
sem_post 函数
【释放信号量】
int sem_post(sem_t *sem);
将信号量++,同时唤醒阻塞在信号量上的线程
【注意点】由于sem_t的实现对用户是隐藏的,所以所谓的++和--操作只能通过函数来实现,而不能直接用++和--符号
【信号量的初值,决定了占用信号量的线程的个数】
练习
- 用环形队列模拟临界区,用信号量实现生产者和消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#define NUM 5
int num[NUM]; //定义一个数组实现环形队列
sem_t blank; //环形队列中的空格数
sem_t product; //环形队列中的产品数
void *producer(void *arg)
{
int i = 0;
while(1)
{
sem_wait(&blank); //生产者将空格数--,为0时阻塞
num[i] = rand() % 100 + 1; //生产一个产品
printf("Produce --- %d\n", num[i]);
sem_post(&product); //将产品数++
i = (i + 1) % NUM; //借助下标实现环形
sleep(rand() % 2);
}
}
void *customer(void *arg)
{
int i = 0;
while(1)
{
sem_wait(&product); //消费者将产品数--,为0时阻塞
printf("Costume ------------- %d\n", num[i]);
num[i] = 0; //消费一个产品
sem_post(&blank); //将空格数++
i = (i + 1) % NUM; //借助下标实现环形
sleep(rand() % 2);
}
}
int main()
{
pthread_t pid, cid;
srand(time(NULL));
sem_init(&blank, 0, NUM);
sem_init(&product, 0, 0);
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, customer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
【运行结果】
3. 揣摩sem_timedwait函数作用。编程实现,一个线程读用户输入,另一个线程打印"hello world"。如果用户无输入,则每隔5秒向屏幕打印一个"hello world";如果有用户输入,立刻打印“hello world”到屏幕
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#include<time.h>
sem_t get;//信号量
void *producer(void*arg){
int i=0;
while(1){
struct timespec ts;
time_t cur = time(NULL); //获取当前时间
ts.tv_sec = cur + 5; //每隔5秒
int r=sem_timedwait(&get, &ts);
if(r){
printf("hello world\n");//超时
}
else{
printf("HELLO WORLD\n");
}
}
}
void *conusm(void*arg){
while(1){
system("stty -echo"); //不回显,不在屏幕上显示输入的字符
//这句代码的作用是,防止把换行符也当做一个字符,如果不加这句,输入一个字符,会打印两句"HELLO WORLD"
system("stty -icanon");//设置一次性读完操作,如使用getchar()读操作,不需要按enter
char ch = getchar();
sem_post(&get);
}
}
int main(){
pthread_t p1,p2;
sem_init(&get,0,0);
pthread_create(&p1,NULL,producer,NULL);
pthread_create(&p2,NULL,conusm,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
sem_destroy(&get);
return 0;
}
【运行结果】