并发程序设计

一、进程的创建和回收

(一)进程的概念

1、进程!=程序

程序是静态的,而进程是动态的,进程是程序执行的一种状态。

并发程序设计_第1张图片

2、进程和程序的区别

并发程序设计_第2张图片

1)进程控制块中包含进程的属性

2)程序在磁盘里面,堆栈都是在内存中,程序运行起来都在内存中

3)进程的运行在内存(RAM)中,手机内存指的是运行内存64G,电脑内存指的是硬盘(ROM)

4)初始化的全局变量在数据段,没有初始化的全局变量放在BSS中

代码段存放可执行文件,机器码

并发程序设计_第3张图片

5) static声明的变量不在栈中,和全局变量放在一起

栈:参数,返回值,局部变量

堆:malloc

并发程序设计_第4张图片

6)一个进程会被分为不同的区域

7)进程控制块(pcb)

PID、进程用户、进程状态、优先级、文件描述符表

3、进程的类型

并发程序设计_第5张图片

1)交互进程:最常用的进程,在前台运行且在shell下可以控制

2)批处理进程:提交到作业队列以便顺序执行

3)守护进程:一直在后台运行,不能用shell命令控制

4、进程的状态

并发程序设计_第6张图片

并发程序设计_第7张图片

(二)进程常用的命令

1、查看进程信息

并发程序设计_第8张图片

1)ps当前状态下的进程

并发程序设计_第9张图片

2)ps -e当前状态下Linux所有的进程

并发程序设计_第10张图片

3)ps -elf Linux下进程的详细信息

并发程序设计_第11张图片

4)进程状态

并发程序设计_第12张图片

5)进程标志

并发程序设计_第13张图片

6)ps下的目录含义

并发程序设计_第14张图片

并发程序设计_第15张图片

PID:进程ID

CMD:进程的名称

NI:进程优先级

PRI:进程优先级

SZ:占用内存

C:占用的CPU利用率

7)ps -elf|grep PID

查看某个进程

2、top实时查看进程信息

并发程序设计_第16张图片

1)翻页shift+'<'(前翻页)或'>'(后翻页)

2)top -p PID查看某个进程

3)ctrl+c退出

3、/proc查看进程的某个目录

4、nice命令:打开一个进程,设置优先级

并发程序设计_第17张图片

越nice优先级越低

 1)一般nice只能调高

2)只有root才能设置为负值

并发程序设计_第18张图片

4)renice改变优先级

并发程序设计_第19张图片

5、后台进程

并发程序设计_第20张图片

6、前台和后台的区别

并发程序设计_第21张图片

./test在前台运行:

正常情况下ctrl+C可以结束进程,下次该进程对应的代码还是前台运行

6、ctrl+Z结束前台运行转为后台

ctrl+Z:此时程序进入停止状态

7、jobs查看后台进程,这时进程已经在后台

8、bg将挂起的进程在后台运行

并发程序设计_第22张图片

9、./test &也可使后台进程运行 

并发程序设计_第23张图片

 10、fg将后台进程重新挂到前台运行

fg+序号进程又重新挂到前台

*(三)创建子进程

1、子进程的概念

在Linux下除了0号进程,其他的进程都是由别人创建的

2、创建子进程

让我们的进程创建子进程

并发程序设计_第24张图片

1)代码

并发程序设计_第25张图片

子进程B的代码和A的代码相同

但子进程B只执行程序中fork函数之后的代码

并发程序设计_第26张图片

并发程序设计_第27张图片

2)打印结果

父进程子进程分别打印

并发程序设计_第28张图片

3)如何让父子进程分别执行

并发程序设计_第29张图片

并发程序设计_第30张图片

(四)父子进程

1、子进程只执行fork之后的代码

2、父子进程执行顺序是操作系统决定的

 3、父子进程的关系

并发程序设计_第31张图片

(五)结束进程

1、若父进程先结束,被init进程收养,子进程变成后台进程

1)分别对父子进程加一个sleep

并发程序设计_第32张图片

父子进程随机运行

并发程序设计_第33张图片

子父进程ID

并发程序设计_第34张图片

kill掉父进程

子进程的父进程变成了2107,也就是init进程

并发程序设计_第35张图片

子进程变成后台进程

并发程序设计_第36张图片

2、如果子进程先结束,父进程没有来得及回收,子进程变成僵尸进程

kill掉父进程

并发程序设计_第37张图片

此时kill掉父进程,子进程消失

(五)子进程进阶

1、孙进程

并发程序设计_第38张图片

并发程序设计_第39张图片

子进程也进行了for循环 

并发程序设计_第40张图片

解决办法:子进程执行结束break

并发程序设计_第41张图片

并发程序设计_第42张图片

(六)进程的退出

并发程序设计_第43张图片

1、exit会刷新缓冲流-库函数-

并发程序设计_第44张图片

2、_exit不会刷新缓冲流-系统调用-

并发程序设计_第45张图片

3、main函数return会隐式调用exit

两个结果相同,其他函数return只是返回上一级

4、exit和return两个结果相同

并发程序设计_第46张图片

并发程序设计_第47张图片

并发程序设计_第48张图片

4、_exit:没有刷新流缓冲区

并发程序设计_第49张图片

(六)进程的回收

*1、wait-回收并查看子进程的情况

并发程序设计_第50张图片

2、status和结束返回2的进程不同

并发程序设计_第51张图片

3、16进制打印200不是2

4、用宏来取得进程的返回值

并发程序设计_第52张图片

并发程序设计_第53张图片

并发程序设计_第54张图片

5、父进程对子进程的回收

1)不回收

并发程序设计_第55张图片

2)回收

并发程序设计_第56张图片

僵尸进程:子进程结束,但是没有被父进程回收

*(二)waitpid进程回收

1、waitpid

并发程序设计_第57张图片

并发程序设计_第58张图片

2、指定pid

3、WNOHANG不阻塞,直接执行,如果当前子进程还未结束,则会出错

出错:

并发程序设计_第59张图片

修正:休眠2秒,等子进程结束

并发程序设计_第60张图片

并发程序设计_第61张图片

二、exec函数族

(一)exec函数族的执行过程

1、父进程的父进程是shell

2、函数族-有很多个函数组成

并发程序设计_第62张图片

3、通过调用exec函数族执行某个程序

4、调用exec函数族进程当前内容被指定的程序替换

5、相当于父子进程执行不同程序

并发程序设计_第63张图片

并发程序设计_第64张图片

(二)execl函数和execlp函数

并发程序设计_第65张图片

1、execl-需要写全部路径

并发程序设计_第66张图片

相当于命令“ls -a -l”

并发程序设计_第67张图片

并发程序设计_第68张图片

2、execlp-不需要写全部路径

并发程序设计_第69张图片

执行效果和execl相同

并发程序设计_第70张图片

(三)execv函数和execvp函数

并发程序设计_第71张图片

并发程序设计_第72张图片

1、execv-全部路径-将命令定义成数组

并发程序设计_第73张图片

并发程序设计_第74张图片

2、execvp-不需要全部路径-数组

并发程序设计_第75张图片

并发程序设计_第76张图片

(四)system函数

并发程序设计_第77张图片

1、system的实现

并发程序设计_第78张图片

并发程序设计_第79张图片

*(五)exec函数族特点

1、exec函数族-进程当前内容被指定内容替换,exec函数以后的程序没有被执行

并发程序设计_第80张图片

并发程序设计_第81张图片

2、第0个参数不使用但是要写

并发程序设计_第82张图片

3、父子进程执行不同程序

并发程序设计_第83张图片

并发程序设计_第84张图片

三、GDB调试

(一)GDB调试多进程程序

1、想要用GDB调试,编译加-g

gcc -g -o 编译生成的名字 文件名

2、进入gdb模式命令

gdb 可执行文件

并发程序设计_第85张图片

3、quit 退出

4、start开始调试

5、n进入下一步

6、默认跟踪父进程

1)set follow-

显示可以跟踪的进程

2)set follow-fork-mode

可以继续选择跟踪父子进程

3)跟踪孩子

set follow-fork-mode child

4)同时调试父子进程

并发程序设计_第86张图片

调试子进程,同时可以切换父进程

5)切换调试的进程

info inferiors查看可以运行的进程

并发程序设计_第87张图片

6)切换跟踪进程

inferior 序号

并发程序设计_第88张图片

7、调试多进程

并发程序设计_第89张图片

四、守护进程

(一)守护进程的概念

并发程序设计_第90张图片

1、守护进程是后台进程,始终是后台运行

2、守护进程独立于终端

3、进程组-会话-控制终端

与控制终端连接的进程就是控制进程。

并发程序设计_第91张图片

1)进程组

并发程序设计_第92张图片

2)会话

一个终端界面可以理解成一个会话

3)控制终端-就是终端

4、守护进程

5、守护进程是孤儿进程

6、守护进程的创建

并发程序设计_第93张图片

1)创建子进程,然后令子进程变成孤儿进程,被init收养,变成后台进程

并发程序设计_第94张图片

此时子进程已经被init收养

2)创建会话-setsid

称为新的会话组长

并发程序设计_第95张图片

并发程序设计_第96张图片

并发程序设计_第97张图片

并发程序设计_第98张图片

自己当家做主人了 

并发程序设计_第99张图片

3)改变工作目录-不被之前的父进程目录局限

并发程序设计_第100张图片

4)重设掩码-创建文件权限(不是必须的)

并发程序设计_第101张图片

5)关闭打开的文件描述符 

并发程序设计_第102张图片

关闭文件描述符后,不会在打印到屏幕

并发程序设计_第103张图片

并发程序设计_第104张图片

守护进程不能在屏幕上打印东西,不能接收键盘的输入

6)nohup ./test.c &:将前台进程转为后台进程

没有写守护进程变成后台的方法

并发程序设计_第105张图片

并发程序设计_第106张图片

守护进程的实现

五、线程的创建和参数传递

(一)线程的基本特点

1、线程共享相同的地址空间和全局变量

并发程序设计_第107张图片

2、线程是在windows下创建的,被Linux引用,Linux不分线程和进程

3、线程的特点

并发程序设计_第108张图片

4、线程共享资源

并发程序设计_第109张图片

常用:静态数据、文件描述符、工作目录

5、  线程的私有资源

并发程序设计_第110张图片

常用:错误号、堆栈

(二)使用pthread库函数创建线程

1、pthread线程库

线程不是通过Linux内核实现,而是由线程库来实现。

并发程序设计_第111张图片

2、创建线程

并发程序设计_第112张图片

(三)常见编译错误及处理方法

链接错误线程编译要加上-lpthread

(四)线程的运行特点

1、线程创建需要时间,如果主进程退出,线程不能得到执行,马上退出

并发程序设计_第113张图片

主进程退出,创建的线程也会退出 

2、sleep 1秒,线程创建成功

并发程序设计_第114张图片

(五)线程id的获取

1、线程的退出

并发程序设计_第115张图片

2、pthread_exit()函数-常用于清理线程

并发程序设计_第116张图片

3、打印tid

1)主函数中,直接获取

并发程序设计_第117张图片

2)pthread_self()函数-函数中获得自己的id

并发程序设计_第118张图片

并发程序设计_第119张图片

3)打印pid

**(六)线程的参数传递

1、通过地址传递参数,注意类型转换

void定义下的arg是任意类型,直接转换成Int类型

并发程序设计_第120张图片

并发程序设计_第121张图片

2、也可以值传递-可能会报警,需要程序员自己保证数据长度正确

并发程序设计_第122张图片

3、建立多个线程并分别向其传参

并发程序设计_第123张图片

并发程序设计_第124张图片

(七)段错误的原因及处理方法

并发程序设计_第125张图片

六、线程的回收及内存演示

(一)pthread_join与pthread_exit

并发程序设计_第126张图片

相当于进程中的wait函数

1、pthread_join主函数对线程的回收-阻塞函数,线程没有回收则等待

pthread_join在主函数中,接收线程结束的返回值

并发程序设计_第127张图片

2、pthread_exit 线程结束函数

(二)pthread_join函数特点

1、一个缺点,如果是单线程回收方便,循环创建多线程,如果最开始的线程没有结束,pthread_join也会阻塞

并发程序设计_第128张图片

(三)线程分离pthread_detach

并发程序设计_第129张图片

1、线程主动与主控线程断开关系,自己可以回收,不需要pthread_join

2、pthread_detach使用

在主函数中:

不阻塞

在线程函数中:

并发程序设计_第130张图片

3、线程属性分离

并发程序设计_第131张图片

并发程序设计_第132张图片

并发程序设计_第133张图片

4、回收线程的三种方式

1)pthread_join(局限):阻塞,多线程阻塞
2)pthread_detach
3)创建线程时分离属性

并发程序设计_第134张图片

(四)线程回收内存演示

查看内存命令: top -p 进程号

线程回收前

等待线程回收后

七、线程的取消和清理

(一)pthread_cancel与线程取消点

1、查看线程-L大写

ps -eLf |grep detach

2、杀掉线程

并发程序设计_第135张图片

1)必须有取消点-阻塞

并发程序设计_第136张图片

2)gdb命令:bt-打印调用栈

(二)GDB调试段错误

(三)pthread_testcancel手动设置取消点

1、线程取消需要取消点-阻塞

2、如果不确定有没有取消点,可以手动设置一个取消点

并发程序设计_第137张图片

(四)pthread_setcancelstate设置取消属性

并发程序设计_第138张图片

1、取消取消点

并发程序设计_第139张图片

2、恢复其他地方取消点

并发程序设计_第140张图片

并发程序设计_第141张图片

3、主函数中的sleep

并发程序设计_第142张图片

(五)线程取消pthreas_setcanceltype

并发程序设计_第143张图片

(六)线程清理-线程申请了空间但因阻塞取消

1、如果线程申请了一块内存,但线程因为阻塞被取消,则会造成内存泄露

2、线程清理是在线程异常退出之后,进行清理

并发程序设计_第144张图片

3、线程的清理函数本质上是宏定义,必须成对使用

push-pop

并发程序设计_第145张图片

并发程序设计_第146张图片

4、routine函数

并发程序设计_第147张图片

return可以结束线程,但是不能出发清理函数

5、可以cancel自己

两种情况:

1)到取消点取消,取消点之前可以取消

2)立即取消

并发程序设计_第148张图片

八、**互斥锁/读写锁/死锁

(一)互斥锁的概念和使用

1、临界资源

并发程序设计_第149张图片

1)临界资源是只允许一个资源访问的任务,互斥资源

2)外设、磁盘都是临界资源

3)临界区是访问临界资源的代码

(二)互斥机制

1、动态初始化创建

并发程序设计_第150张图片

2、静态方式创建

3、锁的销毁

4、互斥锁的使用

并发程序设计_第151张图片

5、申请锁

并发程序设计_第152张图片

1)两个函数的区别

6、释放锁

并发程序设计_第153张图片

*(三)互斥锁

1、创建两个线程,打印字符到文件中

并发程序设计_第154张图片

主函数:

并发程序设计_第155张图片

1.txt:两个线程随机打印,乱序

并发程序设计_第156张图片

2、使用静态方式定义互斥锁

1)初始化互斥锁

2)添加互斥锁

并发程序设计_第157张图片

3)添加了互斥锁之后的1.txt

并发程序设计_第158张图片

4)如果线程执行多个任务,例如多个文件的写操作,需要定义多个互斥锁

并发程序设计_第159张图片

(四)读写锁概念和使用

1、读写锁-提高线程的读写速度-可同时读

并发程序设计_第160张图片

2、防止读的时候有线程写入

并发程序设计_第161张图片

3、同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁

并发程序设计_第162张图片

4、代码

1)在主函数中定义读写锁

并发程序设计_第163张图片

2)结构体变量声明

3)加锁

并发程序设计_第164张图片

4)结果与互斥锁相同

并发程序设计_第165张图片

结论:读写锁与互斥锁结果相近

5、读写锁-只读

读写锁比互斥锁好的地方是,读写锁允许多个用户一起读。

(五)死锁的避免

1、死锁的概念

并发程序设计_第166张图片

2、代码-死锁是对同一资源也就是同一把互斥锁

1)设置两把锁,两个线程同时调用两个资源

并发程序设计_第167张图片

并发程序设计_第168张图片

3、解决办法

延长休眠时间

1)所有资源使用一把锁,锁越少越好
2)获得1,2的先后顺序相同

九、条件变量的使用及注意事项

(一)条件变量基本使用

并发程序设计_第169张图片

1、线程等待某个资源时,休眠,提高运行效率

2、条件变量函数

1)等待资源
2)信号
并发程序设计_第170张图片

3、步骤

1)初始化条件变量
2)定义互斥量
3)生产者
并发程序设计_第171张图片
4)消费者
并发程序设计_第172张图片

4、代码

1)主函数并发程序设计_第173张图片
2)生产者
并发程序设计_第174张图片
3)消费者
并发程序设计_第175张图片
4)准备条件
并发程序设计_第176张图片
5)结果
并发程序设计_第177张图片

5、条件变量一定要和互斥锁使用

(二)条件变量使用注意事项

十、线程池及gdb调试多线程

(一)线程池的概念

1、线程池的作用

一般线程创建使用完都会被回收

线程的创建和销毁>线程的执行,时间不划算

2、线程池的结构

并发程序设计_第178张图片

1)任务队列

并发程序设计_第179张图片

2)线程池工作线程

并发程序设计_第180张图片

并发程序设计_第181张图片

并发程序设计_第182张图片

并发程序设计_第183张图片

并发程序设计_第184张图片

(二)线程池的实现

并发程序设计_第185张图片

(三)线程的GDB调试

1、主函数

2、设置断点 b

3、run运行程序

4、info thread查看线程

并发程序设计_第186张图片

5、切换线程

并发程序设计_第187张图片

 切换到第3个,设置第六行为断点

6、下一步 next

并发程序设计_第188张图片

7、执行完一个线程后再次查看,线程消失

并发程序设计_第189张图片

8、设置线程锁

并发程序设计_第190张图片

设置之后,除了选定的线程,其他不执行

并发程序设计_第191张图片

十一、有名管道和无名管道

(一)无名管道基础

**(面试题)1、进程间通信的方式

并发程序设计_第192张图片

1)无名管道-亲缘进程
2)有名管道
*3)信号
4)共享内存
5)套接字-网络-进程间通信

进程-进程/主机-主机

开销最大

6)System V IPC-古老

并发程序设计_第193张图片

(二)无名管道的特点

1、概念

并发程序设计_第194张图片

相当于共享内存,通过管道传递消息

2、特点

并发程序设计_第195张图片

1)只能父子或兄弟进程间通信
2)单工通讯-固定读端和写端
3)创建两个文件描述符

3、无名管道的创建

并发程序设计_第196张图片

单工通信-每个进程fd[0]和fd[1]只能用一个

并发程序设计_第197张图片

(三)无名管道的创建

1、父子进程间的通信

并发程序设计_第198张图片

只有读操作之后才能打印buf。

(四)无名管道进阶

1、父进程创建无名管道

并发程序设计_第199张图片

并发程序设计_第200张图片

并发程序设计_第201张图片

2、子进程读写描述符与父进程相同

并发程序设计_第202张图片

3、关闭管道

父子进程都可以关闭读写管道,无论这一段是读还是写

并发程序设计_第203张图片

并发程序设计_第204张图片

4、同一个进程,自己和自己无法读写

子进程之间相互通信:

并发程序设计_第205张图片

5、管道可以用于大于两个进程的共享

6、可以两个子进程读/写,父进程写/读

7、无名管道的读写特性

并发程序设计_第206张图片

并发程序设计_第207张图片

8、无名管道大小是64K

(五)有名管道的特点

1、有名管道的特点

并发程序设计_第208张图片

有名管道通过路径名操作,在文件系统中可见,但内容存放在内存中,非亲缘进程间通信,单工通信,先进先出。

(六)有名管道的创建

1、创建有名管道

并发程序设计_第209张图片

路径不能是Linux和wins共享目录

2、直接创建在根目录下

并发程序设计_第210张图片

(七)有名管道的读写

1、写进程代码

并发程序设计_第211张图片

2、读进程代码

并发程序设计_第212张图片

3、读写结果

并发程序设计_第213张图片

并发程序设计_第214张图片

(八)有名管道的注意事项

1、不能使用读写方式打开文件

2、文件可以创建在根目录下,但是不能创建在共享目录下

并发程序设计_第215张图片

3、默认情况下只写O_RDWR和只读O_RDONLY参数是阻塞的,加了第二个参数,不会阻塞,没有内容可读,直接退出。

1)阻塞

2)非阻塞

读文件时没有内容直接结束,有内容读内容

写文件打开失败情况:

并发程序设计_第216张图片

注意事项

1、BSS存放没有初始化的全局变量。

2、static声明的变量不在栈中,和全局变量放在一起。

3、栈:参数,返回值,局部变量

堆:malloc

4、ps -elf 查看Linux下进程的详细信息

ps -elf|grep PID查看某个进程

top动态查看Linux下进程的实时信息

top -p PID查看某个进程

/proc查看进程的某个目录

5、子进程和父进程执行的代码相同,但子进程只执行程序中fork函数之后的代码(也就是子进程创建后的代码)。

6、fork函数创建子进程成功后,父进程返回子进程的进程号(进程号大于0),子进程返回0。

7、若父进程先结束,子进程变成孤儿进程,被init进程收养变成孤儿进程;若子进程先结束,父进程没有及时回收,子进程变为僵尸进程。

8、子进程通过exit/_exit/return返回,父进程通过wait(&status)回收子进程,wait有返回值。

9、exec函数使得进程当前内容被指定内容替换,exec函数以后的程序没有被执行自己,子进程调用exec函数族,父进程不受影响。

并发程序设计_第217张图片

并发程序设计_第218张图片

并发程序设计_第219张图片

并发程序设计_第220张图片

10、gdb调试命令:gcc -g -o 编译生成的名字 文件名

11、关闭文件描述符1,2,3,守护进程不能在屏幕上打印东西,不能接收键盘的输入。

12、内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数,打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。文件描述符0, 1, 2 默认为 stdin stdout stderr。

13、pthread_join在主函数中,接收线程结束的返回值。pthread_exit 线程结束函数,在线程内,返回值可以是一句提示语句。
14、如果线程申请了一块内存,但线程因为阻塞被取消,则会造成内存泄露

15、线程的清理函数本质上是宏定义,必须成对使用push-pop。

16、外设、磁盘都是临界资源,临界区是访问临界资源的代码。

17、读写锁与互斥锁结果相近,读写锁比互斥锁好的地方是,读写锁可以允许多个用户一起读。

18、条件变量一定要和互斥锁使用

19、无名管道只能父子或兄弟进程间通信,无名管道是单工通讯,有固定读端和写端,需要创建两个文件描述符,pfd[0]用于读管道,pfd[1]用于写管道。

20、有名管道不能使用读写方式打开文件,管道文件可以创建在根目录下,但是不能创建在共享目录下。

21、调用open函数时,可以指定打开的文件描述符是以阻塞方式还是以非阻塞方式。
阻塞概念:read函数在读设备或者管道,或者socket的时候,默认是阻塞的,也就是说,对方如果没有发送数据过来,则read函数就会一直等待数据过来,从代码的角度来说,就是read函数后面的代码不会被执行。
非阻塞概念:read函数在读设备或者管道,或者socket的时候,对方如果没有发送数据过来,read函数也会立即返回,从代码的角度来说,就是read函数后面的代码会马上被执行。

你可能感兴趣的:(java,服务器,前端)