进程间通讯
通讯方式:管道、信号、消息队列、共享内存、网络
管道
管道分类:无名管道(父子进程之间通讯),有名管道(任意进程之间通讯)
管道本质:在内存建立一段缓冲区,由操作系统内核来负责创建于管理
无名管道
特点
单向通讯,只能用于父子进程,发送端和接受端称为写端和读端,无名管道抽读端和写端抽象为两个文件进行操作,无名管道创建成功后会返回读端和写端的文件描述符。当管道为空时,读进程会阻塞
函数
#include <unistd.h>
/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES /
struct fd_pair {
long fd[2];
};
struct fd_pair pipe();
/ On all other architectures */
int pipe(int pipefd[2]); //pipefd[0] 读端文件描述符 pipefd[1]写端描述符 成功返回1 失败返回0
**eg:**创建子进程,并向子进程发送消息(先创建无名管道,后创建子进程)
有名管道
特点
有名管道在文件系统中是可见的文件,但是不占用磁盘空间,任然在内存中(占用内存空间),可以通过 mkfifo 命令创建有名管道.
有名管道用于在任意进程之间通讯,当管道为空时,读进程会阻塞
mkfifo函数(Linux 同名函数)
示例
信号
kill -l 可以查看系统信号
相关函数
raise
kill
pause
自定义处理函数
signal函数
strsingal
alarm函数(定时器信号)
子进程退出(自定义信号处理)
消息队列
IPC:Inter-Process Communicate
IPC对象:消息队列、共享内存、信号量
IPC是由内核维护的若干个对象,通过ipcs命令查询
每个ipc对象都有唯一的id号,通过ftok函数生成
消息队列(内核维护)支持任意两个进程之间(多个进程可以使用)通讯,具有IFIO的特性
创建消息队列 msgget
控制消息队列 msgctl
消息队列的发送与接收 msgsnd,magrcv
共享内存
创建共享内 shmget
控制共享内存 shmctl
共享内存映射 shmat
共享内存映射解除 shmdt memcpy(拷贝共享内存至数组)
信号量机制实现进程同步
解决不同进程对临界资源的竞争问题。
临界资源:同一时刻只能被一个进程使用的设备
临界区:使用临界资源的代码段
互斥:同一时刻只有一个进程访问临界资源
同步:在互斥的基础上使临界资源有序访问
Linux中查询信号量的使用 ipcs -s
eg:不使用信号量,父子进程同时向stdout输出
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
// pid_t fork(void);
int main(void){
pid_t cpid;
cpid = fork();
if(cpid == -1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
if(cpid == 0 ){//子进程
for(;;){
printf("----------------------------------\n");
printf("C start.\n");
sleep(1);
printf("C end. \n");
printf("----------------------------------\n");
}
}
else if(cpid > 0){//父进程
for(;;){
printf("----------------------------------\n");
printf("F start.\n");
sleep(1);
printf("F end. \n");
printf("----------------------------------\n");
}
wait(NULL);
}
return 0;
}
结果
root@wangjudealy:/learn# ./a.out
----------------------------------
F start.
----------------------------------
C start.
F end.
----------------------------------
----------------------------------
F start.
C end.
----------------------------------
----------------------------------
C start.
F end.
----------------------------------
----------------------------------
F start.
C end.
----------------------------------
----------------------------------
C start.
^C
创建信号量集合
一个信号量集合包含多个信号量,从0开始编号
semget
创建
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SEM_SET_PATHNAME "."
#define SEM_SET_PRO_ID 103
//int semget(key_t key, int nsems, int semflg);
// pid_t fork(void);
int main(void){
//创建信号量集合
//1、创建key
key_t key;
int semid;
pid_t cpid;
key = ftok(SEM_SET_PATHNAME,SEM_SET_PRO_ID);
if(key == -1){
perror("[ERROR] ftok():" );
exit(EXIT_FAILURE);
}
//2、创建信号量集合
semid=semget(key,1,IPC_CREAT|0644);
if(key == -1){
perror("[ERROR] semget():" );
exit(EXIT_FAILURE);
}
/*
cpid = fork();
if(cpid == -1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
if(cpid == 0 ){//子进程
for(;;){
while(P==1){};
P==1;
printf("----------------------------------\n");
printf("C start.\n");
printf("C end. \n");
printf("----------------------------------\n");
P=0;
}
}
else if(cpid > 0){//父进程
for(;;){
while(P == 1){};
P=1;
printf("----------------------------------\n");
printf("F start.\n");
sleep(1);
printf("F end. \n");
printf("----------------------------------\n");
P=0;
}
wait(NULL);
}
*/
return 0;
}
查看
root@wangjudealy:/learn# ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x6703f601 0 root 644 1
初始化 semctl
操作信号量 semop
代码
实现互斥
sem.h
#ifndef __SEM_H_
#define __SEM_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#endif
//create and init sem
extern int sem_create(int nsems,unsigned short values[]);
//opteion sem
extern int sem_p(int semid , int semnum);
//free sem
extern int sem_v(int semid,int semnum);
//delete sem
extern int sem_del(int semid);
sem.c
#include "sem.h"
#define SEM_SET_PATHNAME "."
#define SEM_PRO_ID 99
union semnu{
unsigned short *array;//SETALL
};
int sem_create(int nsems,unsigned short values[]){
key_t key;
int semid,ret;
union semnu s;
key = ftok(SEM_SET_PATHNAME,SEM_PRO_ID);
if(key == -1){
perror("[ERROR] ftok():");
return -1;
}
semid = semget(key,nsems,IPC_CREAT|0644);
if(semid == -1){
perror("[ERROR] semget():");
return -1;
}
s.array = values;
ret = semctl(semid,0,SETALL,s);
if(ret == -1){
perror("[ERROR] semctl():");
return -1;
}
return semid;
}
int sem_p(int semid , int semnum){
struct sembuf sops;
sops.sem_num = semnum;
sops.sem_op=-1;
sops.sem_flg=SEM_UNDO;//process end,auto free sem
return semop(semid,&sops,1);
}
int sem_v(int semid , int semnum){
struct sembuf sops;
sops.sem_num = semnum;
sops.sem_op=1;
sops.sem_flg=SEM_UNDO;//process end,auto free sem
return semop(semid,&sops,1);
}
int sem_del(int semid){
return semctl(semid,0,IPC_RMID,NULL);
}
main.c
#include "sem.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void){
pid_t cpid;
int semid;
unsigned short value[]={1};
semid = sem_create(1,value);
if(semid == -1){
perror("[ERROR] sem_create():");
exit(EXIT_FAILURE);
}
cpid = fork();
if(cpid == -1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
if(cpid == 0 ){//子进程
for(;;){
sem_p(semid,0);
printf("----------------------------------\n");
printf("C start.\n");
printf("C end. \n");
printf("----------------------------------\n");
sem_v(semid,0);
}
}
else if(cpid > 0){//父进程
for(;;){
sem_p(semid,0);
printf("----------------------------------\n");
printf("F start.\n");
sleep(1);
printf("F end. \n");
printf("----------------------------------\n");
sem_v(semid,0);
}
wait(NULL);
}
return 0;
}
makefile
all:
@gcc main.c sem.c -o main
结果
F start.
F end.
----------------------------------
----------------------------------
C start.
C end.
----------------------------------
----------------------------------
F start.
F end.
----------------------------------
----------------------------------
C start.
C end.
----------------------------------
----------------------------------
F start.
F end.
----------------------------------
----------------------------------
C start.
C end.
----------------------------------
----------------------------------
F start.
F end.
----------------------------------
实现信号量同步
线程
一个进程可以创建多个线程,多个线程共享父进程的内存空间
线程共享资源:进程地址空间,文件描述符表,每种信号的处理方式,当前工作目录,用户id和组id
线程独立资源:线程栈,线程上下文信息,线程ID,寄存器的值,errno变量,信号屏蔽字,调度优先级
线程相关命令
pidstat -t -p pid
-t:显示指定进程关联线程
-p:指定进程pid
top -H -p pid
ps -T -p pid
线程的创建
pthread_create 创建一个线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
pthread_t:线程ID变量指针
const pthread_attr_t:线程属性设置,可谓NULL
start_routine:线程执行函数指针
arg:线程执行函数参数
return:成功返回0,失败返回错误代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <pthread.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
//线程执行函数,直接由操作系统调度执行
void* do_thread(void *arg){
printf("\033[43mThread start.\033[0m\n");
}
int main(void){
pthread_t tid;
int err;
err = pthread_create(&tid,NULL,do_thread,NULL);
if(err != 0){
fprintf(stderr,"[ERROR] pthread_create(): %s \n",strerror(err));
exit(EXIT_FAILURE);
}
while(1){}
return 0;
}
root@wangjudealy:/learn/day15# gcc thread_test.c -lpthread
root@wangjudealy:/learn/day15# ./a.out
Thread start.
^C
问题:
1、编译时独立链接pthread库
2、如果主进程立即结束,会导致线程结束来不及执行
线程的退出 pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
Compile and link with -pthread.
retval:线程返回值
return: success 0 else -1
线程等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
改进代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <pthread.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
//线程执行函数,直接由操作系统调度执行
void* do_thread(void *arg){
printf("\033[43mThread start.\033[0m\n");
pthread_exit(NULL);//线程退出,释放资源
}
int main(void){
pthread_t tid;
int err;
err = pthread_create(&tid,NULL,do_thread,NULL);
if(err != 0){
fprintf(stderr,"[ERROR] pthread_create(): %s \n",strerror(err));
exit(EXIT_FAILURE);
}
pthread_join(tid,NULL);//等待子线程退出,等待时阻塞主进程
return 0;
}
效果
root@wangjudealy:/learn/day15# gcc thread_test.c -lpthread
root@wangjudealy:/learn/day15# ./a.out
Thread start.
root@wangjudealy:/learn/day15#
线程分离
可结合线程:能够被其他线程回收资源并杀死,在不被其他线程回收前,他的资源不释放。线程创建默认状态是可结合的,可由其他线程调用pthread_join函数等待子线程退出并释放相关资源
可分离进程:不能被其他线程回收或者杀死,该线程资源在终止时由系统来释放
pthread_detach 线程分离,该线程资源在终止时由系统来释放,主线程不阻塞
创建多个线程
由主进程统一创建、释放资源或者分离进程,不要递归创建
多个线程做相同任务
可以通过循环多次创建
多个线程做不同任务
创建多个线程函数
线程间通讯
进程实现同步的方式也适用于线程之间
主进程向线程传递参数
pthread_create 的第四个参数 arg 可以向线程传递参数
线程间的互斥
线程互斥锁
pthread_mutex_t 类型的变量
pthread_mutex_t v;
v =1 可以访问 v=0,不能访问(类似于 value=1的信号量)
使用时 使用lock函数
int pthread_mutex_lock(pthread_mutex_t *v);
使用完需要释放锁
int pthread_mutex_unlock(pthread_mutex_t *v);
static pthread_mutex_t v = PTHREAD_MUTEX_INITIALIZER; //全局变量,静态初始化
//其他代码
pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
//访问临界资源
pthread_mutex_unlock(&v);//释放锁
pthread_mutex_t 类型的变量的初始化
1、静态初始化
pthread_mutex_t P_LOCK= PTHREAD_MUTEX_INITIALIZER;
2、动态初始化
涉及两个函数 pthread_mutex_init pthread_mutex_destory
static pthread_mutex_t v;
//其他代码
//初始化
pthread_mutex_init(&v,NULL);
//使用锁
//...............
//销毁锁
ptherad_mutex_destory(&v);
线程的同步
1、信号量
2、条件变量(生产者和消费者模型)
基于互斥锁实现生产者和消费者模型
/*消费者*/
for(;;){
pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
if(conditional statement){//消费
//访问临界资源
}
pthread_mutex_unlock(&v);//释放锁
if(conditional statement){
//是否继续消费
break;
}
}
/*生产者*/
for(;;){
pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
if(conditional statement){//生产
//访问临界资源
}
pthread_mutex_unlock(&v);//释放锁
if(conditional statement){
//是否继续生产
break;
}
}
需要记录生产总数和消费总数,消费和生产总数要对应的上
基于条件变量实现生产者和消费者模型
条件变量:允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知
pthread_cond_t类型变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞在这个条件变量上的线程
静态初始化
动态初始化
static pthread_mutex_t v = PTHREAD_MUTEX_INITIALIZED;
static pthread_cond_t cond = PTHREAD_COND_INITLIZED;
/*消费者*/
for(;;){
pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
while{//不满足条件
pthread_cond_wait(&cond,&v);//1、解锁 2、阻塞在条件变量上 3、在条件变量收到唤醒信号,重新竞争锁
}
while(conditional statement){//满足条件,消费
//访问临界资源
}
pthread_mutex_unlock(&v);//释放锁
if(conditional statement){
//是否继续消费
break;
}
}
/*生产者*/
for(;;){
pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
if(conditional statement){//生产
//访问临界资源
}
pthread_mutex_unlock(&v);//释放锁
//生产
pthread_cond_signal(&cond);//唤醒
if(conditional statement){
//是否继续生产
break;
}
}
io模型
io的本质是基于操作系统接口来控制底层硬件之间的数据传输,并且在操作系统中实现了多种不同的IO模型。
常用的几种由:
1、阻塞型IO
2、非阻塞型IO
3、多路复用IO
阻塞型IO
非阻塞型IO
实现非阻塞IO
设置一般由两种方式
1、通过fcntl函数进行设置
2、通过open、函数在打开文件时设置
fcntl
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int flags;
char buffer[16]={0};
flags = fcntl(0,F_GETFL); //获取标准输入的文件状态
flags = O_NONBLOCK;//追加非阻塞标志
fcntl(0,F_SETFL,flags); //重新设置标志
while(1){ // 不断循环
fgets(buffer,sizeof(buffer),stdin);
printf("buffer : %s \n",buffer);
}
return 0;
}
多路复用IO
多路复用方案:
1、select
2、poll
3、epoll
select多路复用
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
int main(void)
{
int ret;
int maxfd = 0;
fd_set readfds,tmpfds;
struct timeval tv = {3,0},tmp_tv;
char buffer[64] = {0};
FD_ZERO(&readfds);
FD_SET(0,&readfds);// 将标准输入读文件描符合集合中
for(;;){
tmp_tv = tv;
tmpfds = readfds;
ret = select(maxfd + 1,&tmpfds,NULL,NULL,&tmp_tv);
if (ret == -1){
perror("[ERROR] select(): ");
exit(EXIT_FAILURE);
}else if (ret == 0){ // 超时返回
printf("Timeout.\n");
}else if (ret > 0){
if (FD_ISSET(0,&tmpfds)){ // 判断是否在集合中
fgets(buffer,sizeof(buffer),stdin);
printf("buffer : %s ",buffer);
}
}
}
return 0;
}