一、资源竞争解决技术
上篇介绍互斥锁机制用来解决互斥访问的问题;但线程间仍存在顺序问题,因此我们有了信号量的出现。
P操作:使用这个资源(个数减1)。尝试获取资源;有则用,无则等。
V操作:产生这个资源(个数加1)。释放一个资源;如果有任务则等,无任务则用。
1、公共资源角色
buf[1024]公共资源。
(1)写线程——写资源
可写数据条件:①开始,有空buf; ②读线程,读完了。
p(写资源) //申请资源
V(读资源)//产生资源
(2)读线程——读资源
开始,buf中无数据可读,此时充当写资源。
可读数据条件:写线程结束。
P(读资源)
V(写资源)
2、信号量机制
本质:锁(可顺序操作的一把锁);信号量反映的是资源的个数。
功能:实现了一种可以让线程间有序访问临界资源的方式。
步骤:
(1)信号量的定义
sem_t(信号量类型) sem(信号量变量) //造了一类资源;
(2)信号量的初始化
int sem_init(sem_t *sem , int pshared(使用信号量;0为线程间,非0为进程间) , unsigned int value(资源个数))
无名信号量——线程间通信;有名信号量——进程间通信。
(3)信号量的PV操作
a. int sem_wait(sem_t *sem); //P操作
p操作:使用该资源 资源个数-1
表示使用这个资源,资源个数减1
p操作逻辑
尝试获取资源,
有资源可用,直接使用,资源个数减一
如果没有资源可用,此时等待
b. int sem_post(sem_t *sem); //V操作
v操作:产生该资源 资源个数+1
操作逻辑:
释放一个资源,此时如果有任务,在等待这个资源,这个资源直接给对应的任务;
(4)信号量的销毁
int sem_destroy(sem_t *sem)
例:售票系统,实现两个窗口轮流售票。
void *doWin1(void *arg)
{
while (1)
{
sem_wait(&sem1);
if(ticket <= 0)
{
sem_post(&sem2);
break;
}
--ticket;
printf("win1 sell ticket = %d\n",100-ticket);
sem_post(&sem2);
}
pthread_exit(NULL);
}
void *doWin2(void *arg)
{
while (1)
{
sem_wait(&sem2);
if(ticket <= 0)
{
sem_post (&sem1);
break;
}
--ticket;
printf("win2 sell ticket = %d\n",100-ticket);
sem_post (&sem1);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid[2];
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
int ret = pthread_create(&tid[0],NULL,doWin1,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[1],NULL,doWin2,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
sem_destroy(&sem1);
sem_destroy(&sem2);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
例:创建三个线程,有序的打印how are you;
sem_t sem1;
sem_t sem2;
sem_t sem3;
void *doWin1(void *arg)
{
sem_wait(&sem1);
puts("how");
sem_post(&sem2);
pthread_exit(NULL);
}
void *doWin2(void *arg)
{
sem_wait(&sem2);
puts("are");
sem_post(&sem3);
pthread_exit(NULL);
}
void *doWin3(void *arg)
{
sem_wait(&sem3);
puts("you");
sem_post(&sem1);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid[3];
sem_init(&sem1,0,2);
sem_init(&sem2,0,1);
sem_init(&sem3,0,0);
int ret = pthread_create(&tid[0],NULL,doWin1,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[1],NULL,doWin2,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
ret = pthread_create(&tid[2],NULL,doWin3,NULL);
if (ret != 0)
handle_error_en(ret,"pthread_create fail");
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_join(tid[2],NULL);
return 0;
}
二、进程间的通信
1、进程间通信方式
同一主机:
(1)古老的通信方式:管道:①无名管道 ②有名管道;
③信号;
(2)IPC对象通信:System v BSD Suse fedora kernel.org
④消息队列; ⑤共享内存(*)(最高效); ⑥信号量集(信号量);
不同主机:
(3)socket通信:⑦网络通信。
2、无名管道
(1)创建
int pipe(int pipefd[2](管道两端))
两端:pipefd[0] 读端;pipefd[1] 写段。 单向管道
(2)管道的特点
①管道大小65536字节(64k);②管道操作特点:数据读走之后就清空了。
(3)管道读写规则
1)写管道:
①//写端存在,读端也存在
管道若为空,可一直写,直到写满后阻塞。
②//写端存在,读端不存在
写操作会导致管道破裂。(收到信号SIGPIPE,会使程序结束)
2)读管道:
①//写端存在,读端也存在
可读管道,但若管道中无数据,则读操作阻塞。
②//写段不存在,读端存在
可读管道,但若管道中无数据,则读操作不阻塞。
父进程发送字符给子进程,子进程接受并读取:
int main()
{
int fd[2];
if(pipe(fd) < 0)
{
perror("pipe error\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("error fork\n");
return -1;
}
char buf[100];
if(pid > 0)
{
close(fd[0]);
while(1)
{
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] ='\0';
write(fd[1],buf,strlen(buf) + 1);
if (strcmp(buf,"quit") == 0)
{
wait(NULL);
break;
}
}
}
else if(pid == 0)
{
close(fd[1]);
while(1)
{
read(fd[0],buf,sizeof(buf));
printf("buf = %s\n",buf);
if (strcmp(buf,"quit") == 0)
{
break;
}
}
}
return 0;
}