LV5进程,线程和进程间通讯
5.1进程
1.进程和程序
程序:存放在磁盘上的指令和程序的有序集合,是静态的
进程:执行一次程序所需要分配资源的总称,是动态的
包括了进程的创建,调度,执行和衰亡等过程
2.进程的组成,类型及状态
进程的组成
代码段:用于存放执行程序所需要的代码,机器码
数据段:用于存放已经初始化的全局变量
BSS段:用于存放未初始化的全局变量
堆: 用于存放用户动态分配的内存,即用malloc分配的内存
栈: 用于存放局部变量,函数的地址,参数等,但不含static声明的变量
3进程常用命令
命令 | 格式 | 作用 | 示例 |
---|---|---|---|
ps | ps -elf|grep 进程名 | 查看指定进程的信息 | ps -elf|grep demo1 |
top | top | 动态查看进程信息top | |
/proc /proc | 查看进程详细信息 | /proc | |
nice | nice [-n NI值] | 按用户指定优先级运行程序,NI值0-19,值越大优先级越低 | nice [-n 15] |
renice | renice [优先级] pid号 | 改变正在运行中进程的优先级 | renice [15] 1234 |
jobs | jobs | 查看后台进程 | jobs |
bg | bg 索引号 | 将进程挂起到后台运行 | bg 1 |
fg | fg 索引号 | 将后台进程放到前台运行 | fg 1 |
./进程名 & | ./进程名 & | 将程序转为后台程序运行 | ./demo1 & |
4进程的创建
#include <unistd.h>
pid_t fork(void);
/*
返回值:
成功:父进程返回子进程ID号
子进程返回0
失败:返回EOF
*/
辨析点:
1.fork函数作用:复制fork和其后内容给子进程执行
父进程:父进程执行完fork函数后会继续执行剩余程序
子进程:子进程复制fork函数和其以下的程序来执行
具体见demo1程序
2.返回值:
父进程fork后获取的为子进程的ID号
子进程fork后获取的ID值为0
可以通过fork的返回值来区分父子进程,执行不同程序代码
3.父子进程的执行顺序是由内核来进程调度的
4.父子进程有独立的地址空间,两者不会相互干扰
5.假设父进程先结束,则子进程会变为孤儿进程变init函数收养,并且程序变为后台程序
子进程先结束,父进程为及时回收,则子进程变为僵尸进程
见demo2程序
//demo1 进程的创建
/*
程序目的:
1.测试fork函数的作用
2.测试父子进程的pid返回值
3.实现父子进程如何实现不同代码
*/
#include <stdio.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
printf("before fork\r\n");
pid=fork();
printf("after fork\r\n");
if(pid>0)
{
//父进程
printf("father process id=%d\r\n",pid);
}else if(pid==0)
{
//子进程
printf("child process id=%d\r\n",pid);
}else
{
perror("fork");
return 0;
}
printf("process create finish\r\n");
}
程序运行结果可以看出:
1.”before fork“字符串打印了一次,”after fork“和”process create finish“字符串打印了两次,足以说明
fork函数是将其以下的函数复制了一份给子进程执行
2.pid的返回值:父进程的返回值为1960,子进程的返回值为0,足以验证上述的返回值的现象
5进程的退出
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
void _Exit(int status);
//结束当前进程并且返回状态值
注意点:
1.exit函数返回会刷新流缓存区,其余不会刷新
2.exit和return函数的区别
mian函数中调用return 函数相当于隐式调用exit函数返回某个值
其他函数中调用return函数不会去调用exit函数
6 进程的回收
#include <sys/wait.h>
pid_t wait(int *status);
/*
成功 返回回收子进程的进程号
失败 返回EOF
可调用的宏
WIFEXITED(status) 判断进程是否正常结束
WEXITSTATUS(status) 判断子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status) 判断结束子进程信号类型
*/
pid_t waitpid(pid_t pid,int *status,int option);
/*
成功返回指定回收子进程的pid或0,失败返回EOF
pid可以指定回收哪个子进程或任意子进程
pid>0 只等待ID等于pid的子进程结束(常用)
pid=0等待同一个进程组中的任意子进程,若子进程进入别的进程组,则不予理财
pid<-1等待一个指定进程组中的任意子进程,进程组ID等于pid的绝对值
status保存子进程返回值和结束方式的地址
option指定回收方式 0(阻塞)或WNOHANG(不等待)
*/
注意点:
1.若子进程未结束,wait函数处于阻塞状态
2.多子进程回收,哪个先结束哪个先回收
3.status存放子进程的返回值和结束方式的地址,若为NULL表示直接释放进程PCB进程控制块
//demo2 进程的退出及回收
/*
程序目的:
1.实现进程的退出和回收功能
2.验证父子进程退出的特性
3.使用ps命令查看对应的进程信息
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
printf("before fork\r\n");
pid=fork();
printf("after fork\r\n");
if(pid>0)
{
//父进程
printf("father process id=%d\r\n",pid);
sleep(5);
#if 0
exit(0);
#else
while(1)
{
sleep(1);
}
#endif
}else if(pid==0)
{
//子进程
printf("child process id=%d\r\n",pid);
#if 0
while(1)
{
sleep(1);
}
#else
sleep(5);
exit(0);
#endif
}else
{
perror("fork");
return 0;
}
printf("process create finish\r\n");
}
/*
编译:gcc -o demo2 demo2.c
执行:./demo2
查看进程:ps -elf|grep demo2
*/
执行结果:
实验1:父子进程中 #if 1
输入完ps命令后可以看出生成了两个进程
父进程pid号为1982
子进程pid号为1983
等待5s后父进程退出,再次输入ps命令查看进程信息
可以看出子进程PID=1983,其父进程ID变为了1002
再次输入ps -elf查看进程信息可以得到init进程的id号为1002,即证明了父进程结束后,子进程被init函数收养
此外5s延时结束后,程序退出了执行,但查看进程信息是发现子进程仍在运行,即子进程变为了后台进程
实验2:父子进程#if 0
等待5s后输入ps命令查看进程
可以看出子进程pid=2045后被加上[demo3],即表示为僵尸进程
5.2 exec函数族
1.exec函数族作用及函数
作用:实现程序的替换,让父子进程实现不同的程序.
#include <unistd.h>
int execl(const char *path,const char *arg,.....);
int execlp(const char *file,const char *arg,....);
/*
返回值:
成功:执行替换的程序
失败:返回EOF
参数:
path:执行程序的名称,包含路径
arg :所传递的参数
file:执行的程序,在path系统目录下查找
*/
注意点:
1.arg参数中,第0个参数必须写(未使用到),最后一个参数必须为NULL
//demo3 exec函数族使用
/*
程序目的:
1.使用exec实现程序替换
*/
//exec1.c
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("this is exec test!!\r\n");
}
//gcc -o exec1 exec1.c
//demo2.c
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("before exec/r/n");
execlp("./exec1","0","NULL");
printf("after exec/r/n");
}
//gcc -o demo demo1.c
执行结果:
可以看出执行结果替换了exec1的程序,且execlp函数下方的printf没有被执行
2.守护进程
守护进程:生存周期长,独立于中断并且周期性的执行某种任务或等待处理某种事件
特点:后台进程,孤儿进程,可避免被终端信息打扰
命令行格式:nohup xxx &
相关函数:
//设置进程的会话组ID
pid_t setsid(void);
/*
成功:返回调用进程的会话ID
失败:返回-1,并设置errno
备注:调用了setsid函数的进程,即是新的会长,也是新的组长
*/
pid_t getsid(pid_t pid);
/*
成功:返回进程ID
失败:返回-1
备注:获取会话ID
*/
pid_t getpid(void);
/*
成功:返回进程ID
失败:返回-1
备注:获取进程ID
*/
pid_t getpgid(pid_t pid);
/*
成功:返回进程组ID
失败:返回-1
备注:获取进程组ID
*/
3.守护进程的创建
//STEP1:创建子进程,父进程退出
if(fork()>0)
{
exit(0);
}
//备注:子进程变孤儿进程,被init进程收养,变为后台进程
//STEP2:子进程设置为新会话组组长
if(setsid(pid)<0)
{
perror("setsid");
exit(0);
}
//备注:子进程当家作主
//STEP3:更改当前目录
chdir("/");
chdir("/tmp");
//备注:更改到根目录或者临时目录 确保程序的目录不会被删除
//STEP4:重新设置文件权限
if(umask(0)<0)
{
perror("umask");
exit(0);
}
//STEP5:关闭打开的文件描述符
for(i=0;i<3;i++)
{
close(i);
}
//备注:关闭从父进程继承的标准文件描述符
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid=fork();
if(pid>0)
{
exit(0);
}else if(pid<0)
{
perror("fork");
exit(0);
}
printf("before pid=%d,sid=%d,pgid=%d\r\n",getpid(),getsid(getpid()),getpgid(getpid()));
if(setsid()<0)
{
perror("setsid");
exit(0);
}
printf("after pid=%d,sid=%d,pgid=%d\r\n",getpid(),getsid(getpid()),getpgid(getpid()));
chdir("/");
if(umask(0)<0)
{
perror("umask");
exit(0);
}
close(0);
close(1);
close(2);
}
4.gcc调试多进程
//相关命令
//启动GDB
gcc -o -g demo demo.c
gdb demo
//执行程序
run //单步执行
start //一次性执行
n //下一步
//设置选择分支
set follow-fork-mode child //选择为子分支
set follow-fork-mode parent //选择为父分支
set detach-on-fork on/off //设置调试的分支 ON只调试父或子 OFF两个都调试,一个调试一个暂停
//查看调试进程
info inferiors 进程号 //切换调试进程
5.3 线程
1进程和线程
进程:有独立的栈空间,每个进程都参与内核调度
线程:共享栈空间
LINUX不加以区分线程和进程
2.相关函数
#include <pthread.h>
//创建线程
int pthread_create(pthread_t *thread,pthread_atrr_t *atrr,void *(*rountine)(void *),void *arg);
/*
成功,返回0
失败,分会EOF
参数:
thread: 线程对象
atrr: 线程属性,默认属性一般填写NULL
routine:执行的函数指针
arg: 函数指针参数
*/
//线程退出
void pthread_exit(void *retval);
/*
结束当前进程
参数:
retval:返回的参数,一般被pthread_join获取
注意点:线程参数的传参方式:按值传递和按址传递
*/
//线程号的查看
pthread_t pthread_self(void);
/*
查看线程id号
线程命令行查看命令 ps -eLf|grep 线程名
*/
//线程的回收
int pthread_join(pthread_t thread,void **retval);
/*
成功,返回0
失败,返回EOF
参数:
thread:回收的线程对象
revtal:回收的参数值
注意点:
1.线程结束不意味着线程资源得到释放,需要调用回收函数
2.函数为阻塞函数,指定线程未结束则继续等待
3.线程回收只能一个一个回收
*/
//线程的取消
void pthread_cancle(pthread_t thread);
/*
需要有取消点才能取消线程
取消点:系统阻塞调用 sleep,死循环等
无取消点可设置取消点
*/
//设置取消点
void pthread_testcancel(void);
int pthread_seycancelstate(int state,int oldstate);
/*
参数:
state: PTHREAD_CANCEL_ENABLE 可取消
PTHREAD_CANCEL_DISABLE 不可取消
oldstate: 返回原来状态
*/
int pthread_setcancel(int type,int *oldtype);
/*
参数1:PTHREAD_CANCEL_DEFERRED 到取消点取消
PTHREAD_CANCEL_ASYNCHRONOUS 目标线程立即取消
参数2:返回原来状态
*/
//线程清理
void pthread_cleanup_push(void *rounte(void *),void *arg));
void pthread_cleanup_pop(int execute);
/*
作用:对线程中申请的变量进行释放操作
参数
execute: 0表示为不执行回调函数
1表示为执行回调函数
执行函数的条件:
1.pthread_cancel函数取消掉
2.执行了pthread_exit函数
3.非0参数
注意点:
1,对线程申请的变量进行释放
2.push和pop函数必须成对使用,不然编译会报错
*/
3.辨析点
/*
线程创建的参数传递方式:
1.按值传递
void *func(void *arg)
{
printf("agr=%d\r\n",*(int *)arg);
}
int mian()
{
thread_t thread;
int arg=5;
pthread_create(thread,NULL,func,(void *)&arg);
}
2.按址传递
void *func(void *arg)
{
printf("agr=%d\r\n",(int *)arg);
}
int mian()
{
thread_t thread;
int arg=5;
pthread_create(thread,NULL,func,(void *)arg);
}
//注意点:按值传递时会报警告需要注意所传参数和指针大小相同
*/
/*
线程退出和回收的方法:
1.主线程调用pthread_join()函数实现
2.子线程中调用pthread_detach(pthread_self());
3.主线程在创建线程时,设置线程属性
pthread_attr_t atrr;
pthread_attr_init(&atrr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(&tid,&atrr,func,NULL);
*/
4示例代码
//demo1 线程的创建
/*
程序目的:
1.创建线程,线程的退出及回收
2.线程得参数传递方式
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_tid1(void *arg)
{
pthread_detach(pthread_self());
printf("thread1 printf=%d\r\n",*(int *)arg);
thread_exit("thread11 exit!!\r\n");
}
void *thread_tid2(void *arg)
{
pthread_detach(pthread_self());
printf("thread1 printf=%d\r\n",(int *)arg);
thread_exit("thread12 exit!!\r\n");
}
int mian()
{
pthread_t tid;
void *reval1,*reval2;
int arg=5;
//址传递
pthread_create(&tid,NULL,thread_tid1,&(void *)arg);
//值传递
pthread_create(&tid,NULL,thread_tid2,(void *)arg);
sleep(5);
pthread_join(&reval1);
pthread_join(&reval2);
}
实验结果为:5s后程序自动退出
//demo2 线程得取消
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
printf("This is child thread\n");
//设置取消点不取消线程
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
sleep(5);
//设置取消点
pthread_testcancel();
//设置到取消点取消线程
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
//取消点
while(1){
sleep(1);
}
pthread_exit("thread return");
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&retv);
while(1){
sleep(1);
}
}
//demo3 线程的回收
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void cleanup(void *arg){
printf("cleanup,arg=%s\n",(char*)arg);
}
void cleanup2(void* arg){
printf("cleanup2,arg=%s\n",(char*)arg);
}
void *func(void *arg){
printf("This is child thread\n");
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
pthread_cleanup_push(cleanup,"abcd");
pthread_cleanup_push(cleanup2,"efgh");
//while(1)
{
sleep(1);
}
while(1){
printf("sleep\n");
sleep(1);
}
pthread_exit("thread return");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
sleep(10);
pthread_exit("thread return");
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
pthread_join(tid,&retv);
printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}
5.5 信号量
1.互斥信号量
临界资源 :只允许一个任务或线程访问的资源
临界区 :访问临界资源的代码
互斥机制 :访问临界资源前申请锁,访问资源后释放锁
2.函数
//初始化
#include <pthread.h>
//动态方法
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t * attr);
//静态方法
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/*
返回值:成功返回0,失败返回错误代码
参数:
mutex:指向初始化的互斥锁对象
attr :互斥锁属性,NULL标识缺省属性
*/
//互斥锁销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex)
//申请锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/*
返回值:成功返回0,失败返回错误代码
mutex 指向要初始化的互斥锁对象
pthread_mutex_lock 如果无法获得锁,任务阻塞
pthread_mutex_trylock 如果无法获得锁,返回EBUSY而不是挂起等待
*/
//释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
/*
返回值:成功返回0,失败返回错误代码
mutex 指向要初始化的互斥锁对象
执行完临界区要及时释放锁
*/
//初始化一个读写锁
pthread_rwlock_init()
//读锁定读写锁
pthread_rwlock_rdlock()
//非阻塞读锁定
pthread_rwlock_tryrdlock()
//写锁定读写锁
pthread_rwlock_wrlock()
//非阻塞写锁定
pthread_rwlock_trywrlock()
//解锁读写锁
pthread_rwlock_unlock()
//释放读写锁
pthread_rwlock_destroy()
*/
3.读写锁特性
写锁:写者尝试使用写锁,若当前无任何读者和写着,则立马获取锁,
不然等待直到无读者或其他写者
读锁:读者尝试使用读锁,若当前无读者,则立马获得读锁,不然直到等待无读者
同一时刻,可以有多个读者获得锁,只能有一个写者获得锁
4.死锁现象
线程1占据锁1
线程2占据锁2
线程1和线程均向获得彼此的锁----死锁现象,程序阻塞
解决方法:尽量采用一把锁或调整锁的顺序
5.条件变量使用
//生产资源不可共有,生产资源的时间可以是随机的函数
//等待资源
int pthread_cond_wait()
//等待资源超时退出
int pthread_cond_timedwait();
//等到资源信号释放资源
int pthread_cond_signal();
//广播等待资源信号释放资源
int pthread_cond_broadcast()
6.条件变量使用步骤
//SETP1:信号量初始化
pthread_cond_t cond=PTHREAD_COND_INITIALIZE;
pthread_mutex_t mutex=PTHREAD)_MUTEX_INITIALIZE;
pthread_cond_init(&cond);
//STEP2:生产者线程资源
pthread_mutex_lock(&mutex);
pthread_cond_sigal(&cond);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
//STEP3:消费资源线程
pthread_mutex_lock(&mutex);
while(没有资源)
{
pthread_cond_wait(&cond,&mutex);
}
pthread_mutex_unlock(&mutex);
7.线程池
一个线程的池子,可以循环的完成任务的一组线程集合
特点:
我们平时创建一个线程,完成某一个任务,等待线程的退出。但当需要创建大量的线程时,假设
T1为创建线程时间,T2为在线程任务执行时间,T3为线程销毁时间,当T1+T3 > T2,这时候就不划算了,使用线程池可以降低频繁创建和销毁线程所带来的开销,任务处理时间比较短的时候这个好处非常显著
结构:线程池的基本结构:
1.任务队列,
存储需要处理的任务,由工作线程来处理这些任务
2.线程池工作线程,
它是任务队列任务的消费者,等待新任务的信号
实现步骤:
STEP1
创建线程池的基本结构:
任务队列链表
typedef struct Task;
线程池结构体
typedef struct ThreadPool;
STEP2.
线程池的初始化:
pool_init()
{
创建一个线程池结构
实现任务队列互斥锁和条件变量的初始化
创建n个工作线程
}
STEP3
线程池添加任务
pool_add_task
{
判断是否有空闲的工作线程
给任务队列添加一个节点
给工作线程发送信号newtask
}
STEP4
实现工作线程
workThread
{
while(1){
等待newtask任务信号
从任务队列中删除节点
执行任务
}
}
STEP5
线程池的销毁
pool_destory
{
删除任务队列链表所有节点,释放空间
删除所有的互斥锁条件变量
删除线程池,释放空间
}
8.程序示例
//demo1 互斥量使用
/*
两个线程同时写入文件
*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
//静态声明信号量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
FILE *fp;
void *func2(void *arg){
pthread_detach(pthread_self());
printf("This func2 thread\n");
char str[]="I write func2 line\n";
char c;
int i=0;
while(1){
pthread_mutex_lock(&mutex);
while(i<strlen(str))
{
c = str[i];
fputc(c,fp);
usleep(1);
i++;
}
pthread_mutex_unlock(&mutex);
i=0;
usleep(1);
}
pthread_exit("func2 exit");
}
void *func(void *arg){
pthread_detach(pthread_self());
printf("This is func1 thread\n");
char str[]="You read func1 thread\n";
char c;
int i=0;
while(1){
pthread_mutex_lock(&mutex);
while(i<strlen(str))
{
c = str[i];
fputc(c,fp);
i++;
usleep(1);
}
pthread_mutex_unlock(&mutex);
i=0;
usleep(1);
}
pthread_exit("func1 exit");
}
int main(){
pthread_t tid,tid2;
void *retv;
int i;
fp = fopen("1.txt","a+");
if(fp==NULL){
perror("fopen");
return 0;
}
pthread_create(&tid,NULL,func,NULL);
pthread_create(&tid2,NULL,func2,NULL);
while(1){
sleep(1);
}
}
//demo2:读写锁使用
/*
四个线程,两个线程读,两个线程写
*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
pthread_rwlock_t rwlock;
FILE *fp;
void * read_func(void *arg){
pthread_detach(pthread_self());
printf("read thread\n");
char buf[32]={0};
while(1){
pthread_rwlock_rdlock(&rwlock);
while(fgets(buf,32,fp)!=NULL){
printf("%d,rd=%s\n",(int)arg,buf);
usleep(1000);
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
void *func2(void *arg){
pthread_detach(pthread_self());
printf("This func2 thread\n");
char str[]="I write func2 line\n";
char c;
int i=0;
while(1){
pthread_rwlock_wrlock(&rwlock);
while(i<strlen(str))
{
c = str[i];
fputc(c,fp);
usleep(1);
i++;
}
pthread_rwlock_unlock(&rwlock);
i=0;
usleep(1);
}
pthread_exit("func2 exit");
}
void *func(void *arg){
pthread_detach(pthread_self());
printf("This is func1 thread\n");
char str[]="You read func1 thread\n";
char c;
int i=0;
while(1){
pthread_rwlock_wrlock(&rwlock);
while(i<strlen(str))
{
c = str[i];
fputc(c,fp);
i++;
usleep(1);
}
pthread_rwlock_unlock(&rwlock);
i=0;
usleep(1);
}
pthread_exit("func1 exit");
}
int main(){
pthread_t tid1,tid2,tid3,tid4;
void *retv;
int i;
fp = fopen("1.txt","a+");
if(fp==NULL){
perror("fopen");
return 0;
}
pthread_rwlock_init(&rwlock,NULL);
pthread_create(&tid1,NULL,read_func,1);
pthread_create(&tid2,NULL,read_func,2);
pthread_create(&tid3,NULL,func,NULL);
pthread_create(&tid4,NULL,func2,NULL);
while(1){
sleep(1);
}
}
//demo3 条件变量使用
/*
两个线程,一个线程生产taxi资源
一个线程消费taxi资源
*/
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
pthread_cond_t hasTaxi=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
struct taxi{
struct taxi *next;
int num;
};
struct taxi *Head=NULL;
void *taxiarv(void *arg){
printf("taxi arrived thread\n");
pthread_detach(pthread_self());
struct taxi *tx;
int i=1;
while(1){
tx = malloc(sizeof(struct taxi));
tx->num = i++;
printf("taxi %d comming\n",tx->num);
pthread_mutex_lock(&lock);
tx->next = Head;
Head = tx;
pthread_cond_signal(&hasTaxi);
//pthread_cond_broadcast(&hasTaxi);
pthread_mutex_unlock(&lock);
sleep(1);
}
pthread_exit(0);
}
void *takeTaxi(void *arg){
printf("take taxi thread\n");
pthread_detach(pthread_self());
struct taxi *tx;
while(1){
pthread_mutex_lock(&lock);
while(Head==NULL)
{
pthread_cond_wait(&hasTaxi,&lock);
}
tx = Head;
Head=tx->next;
printf("%d,Take taxi %d\n",(int)arg,tx->num);
free(tx);
pthread_mutex_unlock(&lock);
}
pthread_exit(0);
}
int main(){
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,NULL,taxiarv,NULL);
// sleep(5);
pthread_create(&tid2,NULL,takeTaxi,(void*)1);
pthread_create(&tid2,NULL,takeTaxi,(void*)2);
pthread_create(&tid2,NULL,takeTaxi,(void*)3);
while(1) {
sleep(1);
}
}
//demo4 简单线程池使用
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define POOL_NUM 10
typedef struct Task{
void *(*func)(void *arg);
void *arg;
struct Task *next;
}Task;
typedef struct ThreadPool{
pthread_mutex_t taskLock;
pthread_cond_t newTask;
pthread_t tid[POOL_NUM];
Task *queue_head;
int busywork;
}ThreadPool;
ThreadPool *pool;
void *workThread(void *arg){
while(1){
pthread_mutex_lock(&pool->taskLock);
pthread_cond_wait(&pool->newTask,&pool->taskLock);
Task *ptask = pool->queue_head;
pool->queue_head = pool->queue_head->next;
pthread_mutex_unlock(&pool->taskLock);
ptask->func(ptask->arg);
pool->busywork--;
}
}
void *realwork(void *arg){
printf("Finish work %d\n",(int)arg);
}
void pool_add_task(int arg){
Task *newTask;
pthread_mutex_lock(&pool->taskLock);
while(pool->busywork>=POOL_NUM){
pthread_mutex_unlock(&pool->taskLock);
usleep(10000);
pthread_mutex_lock(&pool->taskLock);
}
pthread_mutex_unlock(&pool->taskLock);
newTask = malloc(sizeof(Task));
newTask->func = realwork;
newTask->arg = arg;
pthread_mutex_lock(&pool->taskLock);
Task *member = pool->queue_head;
if(member==NULL){
pool->queue_head = newTask;
}else{
while(member->next!=NULL){
member=member->next;
}
member->next = newTask;
}
pool->busywork++;
pthread_cond_signal(&pool->newTask);
pthread_mutex_unlock(&pool->taskLock);
}
void pool_init(){
pool = malloc(sizeof(ThreadPool));
pthread_mutex_init(&pool->taskLock,NULL);
pthread_cond_init(&pool->newTask,NULL);
pool->queue_head = NULL;
pool->busywork=0;
for(int i=0;i<POOL_NUM;i++){
pthread_create(&pool->tid[i],NULL,workThread,NULL);
}
}
void pool_destory(){
Task *head;
while(pool->queue_head!=NULL){
head = pool->queue_head;
pool->queue_head = pool->queue_head->next;
free(head);
}
pthread_mutex_destroy(&pool->taskLock);
pthread_cond_destroy(&pool->newTask);
free(pool);
}
int main(){
pool_init();
sleep(20);
for(int i=1;i<=20;i++){
pool_add_task(i);
}
sleep(5);
pool_destory();
}
5.6 进程间通信
1.通讯类型
无名管道
有名管道
信号
共享内存
套接字
2.无名管道
int pipe(int pfd[2]);
/*
成功:0;失败:-1,设置errno
参数:
pfd[0] 读管道 pfd[1] 写管道
*/
注意点:
1.只能用于亲缘关系的进程间通信(父子进程,兄弟进程)
2.管道通信是单工的,一端读,一端写(程序实现设计好)
3. 数据自己读不能自己写
4. 管道可以用于大于2个进程共享
5.管道写端被全部关闭,read返回0 (好像读到文件结尾)
6.写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
7.写管道
管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
管道读端没有全部关闭
管道已满,write阻塞。(管道大小64K)
管道未满,write将数据写入,并返回实际写入的字节数。
3.有名管道
#include <unistd.h>
#include <fcntl.h>
int mkfifo(const char *filename, mode_t mode);
/*
成功时返回0,失败时返回EOF
path 创建的管道文件路径
mode 管道文件的权限,如0666
*/
//打开文件
open(const char *path, O_RDONLY); //1
open(const char *path, O_RDONLY | O_NONBLOCK);//2
open(const char *path, O_WRONLY); //3
open(const char *path, O_WRONLY | O_NONBLOCK);//4
注意点:
1.程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,管道是单工的即单方向传输
2.O_NONBLOCK表示为该函数为非阻塞函数,不加参数默认为阻塞函数
3.如果有多个进程写同一个管道,使用O_WRONLY方式打开管道,如果写入的数据长度小于等于
PIPE_BUF(4K),那么或者写入全部字节,或者一个字节都不写入,系统就可以确保数据决不
会交错在一起。
4.内存映射
指定一块内存区域映射磁盘文件,使得进程可以像访问内存一样访问文件,不必使用read和write函数
函数
//创建共享内存
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
/*
addr:设置映射的内存地址,一般设置为NULL让系统自动选择
length:必须>0,映射地址空间的字节数,可从被映射文件开头offset个字节算起
prot:指定共享内存的访问权限,可选参数
PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)
flags:MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(不推荐使用),MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
fd;表示要映射的文件句柄。如果匿名映射写-1
offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。
*/
//内存释放
int munmap(void *addr, size_t length);
/*
返回值:成功返回0,失败返回-1,并设置errno值。
函数参数:
addr:调用mmap函数成功返回的映射区首地址
length:映射区大小(即:mmap函数的第二个参数)
*/
注意点:
映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。
用于映射的文件大小必须>0
文件偏移量必须为0或者4K的整数倍
一定要检查返回值确保共享内存建立成功后在操作
5.System V共享内存
//使用步骤:
//1.创建/打开共享内存
int shmget(key_t key, int size, int shmflg);
/*
key值可以通过调用ftok函数实现
key_t ftok(const char *path, int id);
path 指定文件名
id 指定id号,范围0-255
size: 内存大小
shmflg:IPC_CREAT|0666
返回值:失败返回-1
*/
//2.映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
参数2填写NULL,表示为自动映射
参数3一般写0,表示为可读写
*/
//3 读写共享内存
//往shmat函数的返回地址中填写数据
//4 撤销共享内存映射
int shmdt(void *shmaddr);
//撤销后不可访问
//5 删除共享内存对象
shmctl(shmid, IPC_RMID, NULL);
6.示例代码
//demo1 无名管道
/*
父进程写数据,子进程读数据
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
int pfd[2];
int re;
char buf[20]={0};
pid_t pid;
re = pipe(pfd);
if(re<0){
perror("pipe");
return 0;
}
printf("%d,%d\n",pfd[0],pfd[1]);
pid = fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid>0){
//close(pfd[0]);
while(1){
strcpy(buf,"hhahahahah");
write(pfd[1],buf,strlen(buf));
sleep(1);
}
}else{
close(pfd[1]);
while(1){
re=read(pfd[0],buf,20);
if(re>0){
printf("read pipe=%s\n",buf);
}
}
}
}
//demo2 有名管道
//fifow.c 进程写数据功能
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
int main(){
int re;
int fd;
char buf[32];
re = mkfifo("/myfifo",0666);
if(re<0){
perror("mkfifo");
//return 0;
}
fd = open("/myfifo",O_WRONLY|O_NONBLOCK);
if(fd<0){
perror("open");
return 0;
}
printf("after open\n");
while(1){
fgets(buf,32,stdin);
write(fd,buf,strlen(buf));
}
}
//fifor.c 进程读数据功能
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h.
int main(){
int re;
int fd;
char buf[32];
/* re = mkfifo("/myfifo",0666);
if(re<0){
perror("mkfifo");
return 0;
}
*/
fd = open("/myfifo",O_RDONLY);
if(fd<0){
perror("open");
return 0;
}
printf("after open\n");
while(1){
re=read(fd,buf,32);
if(re>0){
printf("read fifo=%s\n",buf);
}else if(re==0){
exit(0);
}
}
}
//执行程序前,需要先创建文件myfifo
//demo3 内存映射通讯
//shmw.c 进程写数据
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
strcpy(buf,"hello world");
}
//shmr.c 进程读数据
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
// strcpy(buf,"hello world");
printf("share mem=%s\n",buf);
while(1){
sleep(1);
}
shmdt(buf);
shmctl(shmid, IPC_RMID, NULL);
// printf("detach mem=%s\n",buf);
//demo4 system V共享内存
//mmapw.c
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
int i=0;
while(i<2048){
memcpy((addr+i),"a",1);
i++;
sleep(1);
}
// printf("read=%s\n",(char*)(addr));
}
//mmap_r
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
// memcpy((addr),"99999999999999",15);
while(1){
printf("read=%s\n",(char*)(addr));
sleep(1);
}
}