一、多进程
1.主进程和子进程
多任务处理:使得计算机可以同时处理多个任务听歌的同时QQ聊天、办公、下载文件
程序执行就是一个进程
主程序(就是主进程)中可以包含很多的其他进程。在主进程中添加了子进程,这样每个子进程可以在不同的gpu上跑,就可以实现并行。如果不加多进程,那么只能单进程运行,速度很慢。
可能子进程的函数还没有跑完,可是主进程中已经完了。(比如子进程中有sleep方法)。
2.创建进程
(1)process的类
from multiprocessing import Process
def run(name):
print('子进程运行中,name=%s'%(name))
if __name__=='__main__':
print('父进程启动')
p = Process(target=run,args=('test',))
# target表示调用对象,args表示调用对象的位置参数元组
# (注意:元组中只有一个元素时结尾要加,)
print('子进程将执行')
p.start() # start() 才开始主进程
print(p.name)
p.join() #主进程等待子进程结束
print('子进程结束')
Process类常⽤⽅法:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate()(了解)强制终止进程p,不会进行任何清理操作
p.is_alive():如果p仍然运行,返回True.用来判断进程是否还在运行
p.join([timeout]):主进程等待p终止,timeout是可选的超时时间
Process类常⽤属性:
name: 当前进程实例别名, 默认为Process-N, N为从1开始递增的整数;
pid: 当前进程实例的PID值
(2)创建新的进程还能够使⽤类的⽅式, 可以⾃定义⼀个类, 继承Process类, 每次实例化这个类的时候, 就等同于实例化⼀个进程对象
3.if__name__ ==‘main’:
if name == “main”:说明
一个python的文件有两种使用的方法,第一是直接作为程序执行,第二是import到其他的python程序中被调用(模块重用)执行。
因此if name == ‘main’: 的作用就是控制这两种情况执行代码的过程,name 是内置变量,用于表示当前模块的名字 在if name == ‘main’: 下的代码只有在文件作为程序直接执行才会被执行,而import到其他程序中是不会被执行的 在 Windows 上,子进程会自动 import 启动它的这个文件,而在 import 的时候是会执行这些语句的。如果不加if name == “main”:的话就会无限递归创建子进程 所以必须把创建子进程的部分用那个 if 判断保护起来 import 的时候 name 不是 main ,就不会递归运行了
4.全局变量在多个进程中不共享:进程之间的数据是独立的,默认情况下互不影响
5.进程池:用来创建多个进程
当需要创建的⼦进程数量不多时, 可以直接利⽤multiprocessing中的Process动态生成多个进程, 但如果是上百甚⾄上千个⽬标, ⼿动的去创建进程的⼯作量巨⼤, 此时就可以⽤到multiprocessing模块提供的Pool
6.多进程之间,默认是不共享数据的 通过Queue(队列Q)可以实现进程间的数据传递 Q本身是一个消息队列
可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递初始化Queue()对象时(例如: q=Queue()) , 若括号中没有指定最⼤可接收的消息数量, 或数量为负值, 那么就代表可接受的消息数量没有上限
Queue.qsize(): 返回当前队列包含的消息数量
Queue.empty(): 如果队列为空, 返回True, 反之False
Queue.full(): 如果队列满了, 返回True,反之False
Queue.get([block[, timeout]]): 获取队列中的⼀条消息, 然后将其从列队中移除, block默认值为True
Queue.get_nowait(): 相当Queue.get(False)
Queue.put(item,[block[, timeout]]): 将item消息写⼊队列, block默认值为True
7.进程池创立通信:
进程池创建的进程之间通信:如果要使⽤Pool创建进程, 就需要使⽤multiprocessing.Manager()中的Queue()
⽽不是multiprocessing.Queue()
二、多线程
1.定义
线程:实现多任务的另一种方式一个进程中,也经常需要同时做多件事,就需要同时运行多个‘子任务’,这些子任务,就是线程
一个进程可拥有多个并行的(concurrent)线程,当中每一个线程,共享当前进程的资源
由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快
等着系统一个个的调度,一个个运行
IO的时候不用CPU,用多线程可以更快的读写
2.进程,线程区别
进程:计算密集型用多进程
线程:IO密集型用多线程
进程是系统进⾏资源分配和调度的⼀个独⽴单位
进程在执⾏过程中拥有独⽴的内存单元, ⽽多个线程共享内存, 从⽽极⼤地提⾼了程序的运⾏效率
⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程
线程是进程的⼀个实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独⽴运⾏的基本单位
线程⾃⼰基本上不拥有系统资源,只拥有⼀点在运⾏中必不可少的资源,但是它可与同属⼀个进程的其他的线程共享进程所拥有的全部资源
线程的划分尺度⼩于进程(资源⽐进程少), 使得多线程程序的并发性⾼
线程不能够独⽴执⾏, 必须依存在进程中
线程和进程在使⽤上各有优缺点: 线程执⾏开销⼩, 但不利于资源的管理和保护; ⽽进程正相反
3.创立多线程
python的threading模块
创建线程的两种方式:
第一:通过 threading.Thread 直接在线程中运行函数;
第二:通过继承 threading.Thread 类来创建线程 这种方法只需要重载 threading.Thread 类的 run 方法,然后调用 start()开启线程就可以了
不用t.join() 线程不完,进程不完,所以不用join()
(2)
4.线程5中状态
多线程程序的执⾏顺序是不确定的(操作系统决定)。 当执⾏到sleep语句时, 线程将被阻塞(Blocked) , 到sleep结束后, 线程进⼊就绪(Runnable) 状态, 等待调度。 ⽽线程调度将⾃⾏选择⼀个线程执⾏。 代码中只能保证每个线程都运⾏完整个run函数, 但是线程的启动顺序、run函数中每次循环的执⾏顺序都不能确定
1、新状态:线程对象已经创建,还没有在其上调用start()方法。
2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的(可运行的),但是当前没有条件运行。但是如果某件事件出现,他可能返回到可运行状态。
5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出RuntimeError: threads can only be started once异常。
5.线程g共享全局变量
在⼀个进程内的所有线程共享全局变量, 多线程之间的数据共享(这点要⽐多进程要好)
计算100000次可能导致结果错误:
错误的地方: 可能没赋值成功导致结果错误
6.线程同步
当多个线程⼏乎同时修改某⼀个共享数据的时候, 需要进⾏同步控制
线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引⼊互斥锁
互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性(原子性)
互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
创建锁mutex = threading.Lock()
锁定mutex.acquire()
释放mutex.release() #解锁
死锁(错误情况,理解即可)
在线程间共享多个资源的时候, 如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源, 就会造成死锁 ⼊到了死锁状态, 可以使⽤ctrl-z退出
同步调⽤:确定调用的顺序 线程同步-多个线程有序执⾏
按顺序购买四大名著
异步调⽤:不确定顺序 你 喊 你朋友吃饭 , 你朋友说知道了 , 待会忙完去找你 ,你就去做别的了 无序执行,方式很多
堵塞和非堵塞
7.线程的同步。线程间的通信