IO进程学习——6
一、线程(补)
1.pthread_join 函数
函数功能:
等待线程结束并回收线程产生的资源
函数头文件:
#include <pthread.h>
函数原型:
int pthread_join(pthread_t thread, void **retval);
函数参数说明:
- thread: 需要处理的线程的线程 id
- retval: 获取线程退出的状态信息
函数返回值说明:
成功:返回 0
失败:返回错误码
用法示例:
/*===============================================
* 文件名称:pthread_join.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:
================================================*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *func(void *arg)
{
int *p = (int *)arg;
while(1)
{
sleep(1);
printf("------%d------\n",*p);
if(*p == 3)
{
char *retval = "The thread is exit!";
printf("%p\n",retval);
pthread_exit((void *)retval);
}
(*p)++;
}
}
int main(int argc, char *argv[])
{
int a = 0;
pthread_t tid;
void *retval = NULL;
pthread_create(&tid,NULL,func,(void *)&a);
pthread_join(tid,&retval);
printf("%p\n",retval);
printf("%s\n",(char *)retval);
while(1);
return 0;
}
运行截图
2.pthread_cancel 函数
函数功能:
退出函数函数
函数头文件:
#include <pthread.h>
函数原型:
int pthread_cancel(pthread_t thread);
函数参数说明:
1.thread:需要取消的线程的线程ID
函数返回值说明:
成功:返回0
失败:返回非零的错误码
3.pthread_detach 函数
函数的功能:
线程分离函数,线程结束后资源将自动回收 函数使用需要的头文件: #include <pthread.h> 函数原型: int pthread_detach (pthread_t thread); 函数参数说明: 1.thread: 需要分离的线程的线程 ID 函数返回值说明: 成功:返回 0 失败:返回非零的错误码
二、线程的通信
线程间的通信非常简单,原因是多个线程共用同一片进程空间,所以数据的
交互简单,但是数据交互时需要避免资源竞争问题
如何解决资源竞争问题?
同步互斥机制
同步互斥
1.同步
(1)线程间的同步 PV操作
信号量
代表某一类资源,其值表示系统中该资源的数量
是一个受保护的变量,只能通过三种操作来访问
初始化
P操作(申请资源) V操作(释放资源)
接口函数
sem_init
函数的功能:
生产资源,V操作,生产者,信号量的资源值+1
函数使用需要的头文件:
#include <semaphore.h> 函数原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
函数参数说明:
1.sem:信号量变量指针
2.pshared:设置是进程间同步还是线程间同步,0为线程间同步
3.value:设置初始可以使用资源的值
函数返回值说明:
成功:返回0
失败:返回-1,并且设置全局错误号
sem_post
函数功能:
生产资源,V操作,生产者,信号量的资源值+1函数
函数头文件:
#include <semaphore.h>
函数原型:
int sem_post(sem_t *sem);
函数参数说明:
1.sem:信号量变量指针
函数返回值说明:
成功:返回0
失败:返回-1,并且设置全局错误号
sem_wait
函数功能:
同步机制中的消费者功能,为信号量-1(P操作)
函数头文件:
#include <semaphore.h>
函数原型:
int sem_wait(sem_t *sem);
函数参数说明:
1.sem:信号量变量指针
函数返回值说明:
成功:返回0
失败:返回-1,并且设置全局错误号
(2)练习 多任务需要按照指定顺序运行
/*===============================================
* 文件名称:sem_n.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:资源同步 sem_t 多信号量
================================================*/
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
//同步信号量1 同步线程1(V)和线程2(P)
sem_t s1;
//同步信号量2 同步线程2(V)和线程3(P)
sem_t s2;
//信号量1 生产变量
int flag = 0;
//线程1
void *func1(void *arg)
{
while(1)
{
scanf("%d",&flag);
if(flag == 1)
{
flag = 0;
printf("线程1:sem1---V操作\n");
sem_post(&s1);
}
else if(flag == 2)
{
flag = 0;
printf("线程1:sem2---V操作\n");
sem_post(&s2);
}
}
}
//线程2
void *func2(void *arg)
{
while(1)
{
sem_wait(&s1);
printf("线程2:sem1---P操作\n");
printf("线程2:sem2---V操作\n");
sem_post(&s2);
}
}
//线程3
void *func3(void *arg)
{
while(1)
{
sem_wait(&s2);
printf("线程3:sem2---P操作\n");
}
}
int main(int argc, char *argv[])
{
//初始化信号量
//第二个参数设置为0表示线程间同步
sem_init(&s1,0,0);
sem_init(&s2,0,0);
//线程1
pthread_t tid1;
pthread_create(&tid1,NULL,func1,NULL);
//线程2
pthread_t tid2;
pthread_create(&tid2,NULL,func2,NULL);
//线程3
pthread_t tid3;
pthread_create(&tid3,NULL,func3,NULL);
while(1);
return 0;
}
运行截图
2.互斥
临界资源竞争问题
加锁的形式对争抢到的资源进行互斥访问保护
1.互斥锁 函数
pthread_mutex_init
函数的功能:
初始化线程锁
函数使用需要的头文件:
#include <pthread.h>
函数原型:
int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr)
函数参数说明:
1.mutex:线程锁变量指针
2.attr:线程锁属性的结构体变量指针,通常使用默 认属性,传NULL
函数返回值说明:
成功:返回0
失败:返回-1,并且设置全局错误号
pthread_lock
函数的功能:
线程锁加锁
函数使用需要的头文件:
#include <pthread.h> 函数原型:
int pthread_mutex_lock (pthread_mutex_t *mutex);
函数参数说明:
1.mutex: 线程锁变量指针
函数返回值说明:
成功:返回 0
失败:返回 - 1, 并且设置全局错误号
pthread_unlock
函数的功能:
线程锁解锁
函数使用需要的头文件: #include <pthread.h> 函数原型: int pthread_mutex_unlock(pthread_mutex_t *mutex) 函数参数说明: 1.mutex:线程锁变量指针 函数返回值说明: 成功:返回0 失败:返回-1,并且设置全局错误号
三、进程间通信(难点)
1.管道通信
(1)无名管道pipe(半双工通信)
只能用于亲缘间的进程通信
管道如果一直写入会陷入阻塞状态
如果管道关闭之后,继续往管道写入数据会触发SIGPIPE信号(管道破裂,进程强制结束)
pipe函数(管道函数)
函数的功能:
创建无名管道 函数使用需要的头文件: #include <unistd.h> 函数原型: int pipe(int pipefd[2]); 函数参数说明: 1.pipefd:用于接收无名管道的文件描述符的数组地址 函数返回值说明: 成功:返回0 失败:返回-1,并且设置全局错误号
单工:只能一方发送一方接收(收发方固定)
半双工:双方都可以收和发,但是同一时间只能一方发送一方接收
全双工:双方可以同时收发
(2)有名管道fifo
可以非亲缘间进程通信
有名管道主要借助Linux系统下的管道文件进行通信
mkfifo命令:shell命令效果,创建有名管道
使用方法:mkfifo 有名管道文件名
mkfifo函数
函数的功能:
创建有名管道 函数使用需要的头文件: #include <sys/types.h> #include <sys/stat.h> 函数原型: int mkfifo(const char *pathname, mode_t mode); 函数参数说明: 1.pathname:有名管道的文件路径 2.mode:有名管道的文件的权限说明 函数返回值说明: 成功:返回0 失败:返回-1,并且设置全局错误号
1.1作业
(1)完成简单的聊天窗口
聊天客户端1:fifo_client1.c
/*===============================================
* 文件名称:fifo_client1.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:简单聊天窗实现(客户端1)
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
char buf[1024] = {0};
int ret = -1;
if(-1 == pid)
{
perror("fork");
return -1;
}
else if(0 == pid) //子进程 读取C2发来的信息
{
int fd_read = open("./f2",O_RDONLY); //接收客户端2的信息
while(1)
{
memset(buf,0,1024);
ret = read(fd_read,buf,1024);
if(ret == 0)
{
printf("The C2 has exit!\n");
exit(0);
}
if(ret == -1)
{
perror("C1_read");
exit(0);
}
printf("C2:%s",buf);
}
}
else if(0 < pid) //父进程 将C1发送信息写入FIFO管道
{
int fd_write = open("./f1",O_WRONLY); //发送客户端1信息到客户端2
printf("----------Client_1----------\n");
while(1)
{
memset(buf,0,1024);
fgets(buf,1024,stdin); //获取输入
ret = write(fd_write,buf,strlen(buf));
if(ret == -1)
{
perror("C1_write");
exit(0);
}
}
}
return 0;
}
聊天客户端2:fifo_client2.c
/*===============================================
* 文件名称:fifo_client2.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:简单聊天窗实现(客户端2)
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
char buf[1024] = {0};
int ret;
if(-1 == pid)
{
perror("fork");
return -1;
}
else if(0 == pid) //子进程 读取C1发来的信息
{
int fd_read = open("./f1",O_RDONLY); //接收客户端1的信息
while(1)
{
memset(buf,0,1024);
ret = read(fd_read,buf,1024);
if(ret == 0)
{
printf("The C1 has exit!\n");
exit(0);
}
if(ret == -1)
{
perror("C2_read");
exit(0);
}
printf("C1:%s",buf);
}
}
else if(0 < pid) //父进程 将C2发送信息写入FIFO管道
{
int fd_write = open("./f2",O_WRONLY); //发送客户端2信息到客户端1
printf("----------Client_2----------\n");
while(1)
{
memset(buf,0,1024);
fgets(buf,1024,stdin); //获取输入
ret = write(fd_write,buf,strlen(buf));
if(ret == -1)
{
perror("C2_write");
exit(0);
}
}
}
return 0;
}
运行结果
(2)如何实现多个线程严格按顺序运行
/*===============================================
* 文件名称:static_order.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:同步实现多进程执行顺序同步
================================================*/
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
//同步信号量1 同步线程1(V)和线程2(P)
sem_t s1;
//同步信号量2 同步线程2(V)和线程3(P)
sem_t s2;
//同步信号量3 同步线程3(V)和线程1(P)
sem_t s3;
//线程1
void *func1(void *arg)
{
while(1)
{
sem_wait(&s3);
printf("线程1:\n");
printf(" sem3---P操作\n");
printf(" sem1---V操作\n");
sem_post(&s1);
}
}
//线程2
void *func2(void *arg)
{
while(1)
{
sem_wait(&s1);
printf("线程2:\n");
printf(" sem1---P操作\n");
printf(" sem2---V操作\n");
sem_post(&s2);
}
}
//线程3
void *func3(void *arg)
{
while(1)
{
sem_wait(&s2);
printf("线程3:\n");
printf(" sem2---P操作\n");
printf(" sem3---V操作\n");
sem_post(&s3);
}
}
int main(int argc, char *argv[])
{
//初始化信号量
//第二个参数设置为0表示线程间同步
sem_init(&s1,0,0);
sem_init(&s2,0,0);
sem_init(&s3,0,1);
//线程1
pthread_t tid1;
pthread_create(&tid1,NULL,func1,NULL);
//线程2
pthread_t tid2;
pthread_create(&tid2,NULL,func2,NULL);
//线程3
pthread_t tid3;
pthread_create(&tid3,NULL,func3,NULL);
while(1);
return 0;
}
运行截图
(3)完成群聊效果
Client 代码之一
/*===============================================
* 文件名称:Client_1.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:(基于管道)群聊-客户端1
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
char buf[1024] = {0};
char send[1030] = {0};
int ret = -1;
if(-1 == pid)
{
perror("fork");
return -1;
}
else if(0 == pid) //子进程 向服务器接收数据
{
int fd_read = open("./C1_1",O_RDONLY);
if(-1 == fd_read)
{
perror("C1:fd_read");
exit(0);
}
while(1)
{
memset(buf,0,1024);
ret = read(fd_read,buf,1024);
if(-1 == ret)
{
perror("C1:read");
exit(0);
}
printf("%s",buf);
}
}
else if(0 < pid) //父进程 向服务器发送数据
{
int fd_write = open("./C1_2",O_WRONLY);
if(-1 == fd_write)
{
perror("C1:write");
exit(0);
}
while(1)
{
fgets(buf,1024,stdin);
sprintf(send,"C1:%s",buf);
ret = write(fd_write,send,strlen(send));
if(-1 == ret)
{
perror("C1:write");
exit(0);
}
}
}
while(1);
return 0;
}
server.c
/*===============================================
* 文件名称:server.c
* 创 建 者:青木莲华
* 创建日期:2025年08月11日
* 描 述:(管道实现)群聊-服务器端
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//C1信号量
sem_t s1;
//C2信号量
sem_t s2;
//C3信号量
sem_t s3;
//接收缓冲区
char buf_read[1024] = {0};
char buf_write[1024] = {0};
//Server获取C1信息
void *Client_1_read(void *arg)
{
int c1_read = open("./C1_2",O_RDONLY);
if(-1 == c1_read)
{
perror("c1_fd_read");
exit(0);
}
int ret = -1;
printf("C1_read is connection!\n");
while(1)
{
ret = read(c1_read,buf_read,1024);
if(-1 == ret)
{
perror("c1_read");
exit(0);
}
memset(buf_write,0,1024);
strcpy(buf_write,buf_read);
sem_post(&s2); //接收到C1 通过信息量 来控制发送给C2 C3
sem_post(&s3);
}
}
//Server发送信息到C1
void *Client_1_write(void *arg)
{
int c1_write = open("./C1_1",O_WRONLY);
if(-1 == c1_write)
{
perror("c1_fd_write");
exit(0);
}
int ret = -1;
printf("C1_write is connection!\n");
while(1)
{
sem_wait(&s1); //等待V操作
ret = write(c1_write,buf_write,strlen(buf_write));
if(-1 == ret)
{
perror("c1_write");
exit(0);
}
}
}
//Server获取C2信息
void *Client_2_read(void *arg)
{
int c2_read = open("./C2_2",O_RDONLY);
if(-1 == c2_read)
{
perror("c2_fd_read");
exit(0);
}
int ret = -1;
printf("C2_read is connection!\n");
while(1)
{
ret = read(c2_read,buf_read,1024);
if(-1 == ret)
{
perror("c2_read");
exit(0);
}
memset(buf_write,0,1024);
strcpy(buf_write,buf_read);
sem_post(&s1);
sem_post(&s3);
}
}
//Server发送信息到C2
void *Client_2_write(void *arg)
{
int c2_write = open("./C2_1",O_WRONLY);
if(-1 == c2_write)
{
perror("c2_fd_write");
exit(0);
}
int ret = -1;
printf("C2_write is connection!\n");
while(1)
{
sem_wait(&s2); //等待V操作
ret = write(c2_write,buf_write,strlen(buf_write));
if(-1 == ret)
{
perror("c2_write");
exit(0);
}
}
}
//Server获取C3信息
void *Client_3_read(void *arg)
{
int c3_read = open("./C3_2",O_RDONLY);
if(-1 == c3_read)
{
perror("c3_fd_read");
exit(0);
}
int ret = -1;
printf("C3_read is connection!\n");
while(1)
{
ret = read(c3_read,buf_read,1024);
if(-1 == ret)
{
perror("c3_read");
exit(0);
}
memset(buf_write,0,1024);
strcpy(buf_write,buf_read);
sem_post(&s1);
sem_post(&s2);
}
}
//Server发送信息到C3
void *Client_3_write(void *arg)
{
int c3_write = open("./C3_1",O_WRONLY);
if(-1 == c3_write)
{
perror("c3_fd_write");
exit(0);
}
int ret = -1;
printf("C3_write is connection!\n");
while(1)
{
sem_wait(&s3); //等待V操作
ret = write(c3_write,buf_write,strlen(buf_write));
if(-1 == ret)
{
perror("c3_write");
exit(0);
}
}
}
int main(int argc, char *argv[])
{
//C1信号量初始化
sem_init(&s1,0,0);
//C2信号量初始化
sem_init(&s2,0,0);
//C3信号量初始化
sem_init(&s3,0,0);
//C1读线程
pthread_t pt1_r;
//C1写线程
pthread_t pt1_w;
//C2
pthread_t pt2_r;
pthread_t pt2_w;
//C3
pthread_t pt3_r;
pthread_t pt3_w;
pthread_create(&pt1_r,NULL,Client_1_read,NULL);
pthread_create(&pt2_r,NULL,Client_2_read,NULL);
pthread_create(&pt3_r,NULL,Client_3_read,NULL);
pthread_create(&pt1_w,NULL,Client_1_write,NULL);
pthread_create(&pt2_w,NULL,Client_2_write,NULL);
pthread_create(&pt3_w,NULL,Client_3_write,NULL);
while(1);
return 0;
}
运行截图