进程补充和线程的介绍
一、进程间通信--队列
前面说到进程之间的数据不能直接进行交互,这里用IPC机制进行交互。
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
Queue([maxsize])
创建共享的进程队列。
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
底层队列使用管道和锁定实现。 管道+锁
我们先来了解一些队列的用法:
from multiprocessing import Queue q = Queue(5) # 括号内可以传参数 表示的是这个队列的最大存储数 # 往队列中添加数据 q.put(1) q.put(2) # print(q.full()) # 判断队列是否满了 q.put(3) q.put(4) q.put(5) # print(q.full()) # q.put(6) # 当队列满了之后 再放入数据 不会报错 会原地等待 直到队列中有数据被取走(阻塞态) print(q.get()) print(q.get()) print(q.get()) print(q.empty()) # 判断队列中的数据是否取完 print(q.get()) print(q.get()) print(q.empty()) # print(q.get_nowait()) # 取值 没有值不等待直接报错 # print(q.get()) # 当队列中的数据被取完之后 再次获取 程序会阻塞 直到有人往队列中放入值 """ full get_nowait empty 都不适用于多进程的情况 """
Queue([maxsize])
创建共享的进程队列。maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。
Queue的实例q具有以下方法:
q.get() 当队列中的数据被取完之后 再次获取 程序会阻塞 直到有人往队列中放入值
q.get_nowait( ) 取值 没有值不等待直接报错
q.put() 当队列满了之后 再放入数据 不会报错 会原地等待 直到队列中有数据被取走(阻塞态)
q.empty() 判断队列中的数据是否取完
q.full() 判断队列是否满了
full,get_nowait,empty
都不适用于多进程的情况。如果其他进程和线程正在往队列里面添加项目,这个结果就不可靠了
进程间通信IPC机制
子进程放数据,主进程获取数据 两个进程相互放,取数据
from multiprocessing import Process,Queue def producer(q): q.put('hello GF~') def consumer(q): print(q.get()) if __name__ == '__main__': q = Queue() p = Process(target=producer,args=(q,)) c = Process(target=consumer, args=(q,)) p.start() c.start()
二、生产者消费者模型
生产者:生产/制造数据的
消费者:消费/处理数据的
在并发编程中使用生产者和消费者模式能够解决大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
JoinableQueue
创建可连接的共享进程队列。这就像一个Queue对象,但队列允许项目的使用者通知生产者项目已经被处理成功。通知进程是使用共享的信号和条件变量来实现的。
使用JoinableQueue中的两个方法:
q.task_done() # 告诉队列你已经从队列中取出了一个数据 并且处理完毕了 q.join() # 等到队列中数据全部取出
from multiprocessing import Process,JoinableQueue import random import time def producer(name,food,q): for i in range(10): data = '%s生产了%s%s'%(name,food,i) time.sleep(random.random()) q.put(data) print(data) def consumer(name,q): while True: data = q.get() if data == None:break print('%s吃了%s'%(name,data)) time.sleep(random.random()) q.task_done() # 告诉队列你已经从队列中取出了一个数据 并且处理完毕了 if __name__ == '__main__': q = JoinableQueue() p = Process(target=producer,args=('大厨egon','馒头',q)) p1 = Process(target=producer,args=('跟班tank','生蚝',q)) c = Process(target=consumer,args=('许兆龙',q)) c1 = Process(target=consumer,args=('吃货jerry',q)) p.start() p1.start() c.daemon = True c1.daemon = True c.start() c1.start() p.join() p1.join() q.join() # 等到队列中数据全部取出
三、线程
1.什么是线程:进程线程其实都是虚拟单位,都是帮助我们形象的描述某种事物
进程:资源单位
线程:执行单位
将内存比作工厂,那么进程就相当于是工厂里面的车间,线程就相当于是车间里面的流水线。
注意:每个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中提供代码运行所需要的资源。
进程是资源分配的最小单位,线程是CPU调度的最小单位。
2.为什么要有线程
开进程:
1.申请内存空间 耗资源
2.'拷贝代码' 耗资源
开线程:
一个进程内可以起多个线程,并且线程与线程之间的数据是共享的,开启线程的开销要远远小于开启进程的开销。
仔细观察就会发现进程还是有很多缺陷的,主要体现在这两点:
1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起。
3.进程与线程的关系
4.创建线程的两种方式
第一种方法:
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 # 小的代码执行完 线程就已经开启了 print('主') #结果 线程在开启的时候就会执行,所以先把第一句话打印出来了,如果是在进程里面会先执行主进程的代码。 egon is running 主 egon is over
第二种方法:继承Thread类,重写run方法
from threading import Thread import time class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running'%self.name) time.sleep(3) print('%s is over'%self.name) t = MyThread('egon') t.start() print('主')
5.线程对象及其方法
pid比较:在线程中子线程和主线程的pid是一样的
from threading import Thread import time import os def task(name,i): print('%s is running'%name) print('子',os.getpid()) time.sleep(i) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',1)) t1 = Thread(target=task,args=('jason',2)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.join() # 主线程等待子线程运行完毕,主线程再运行 print('主') print('主',os.getpid())
active_count 查看当前活跃的线程数
from threading import Thread,active_count import time def task(name,i): print('%s is running'%name) time.sleep(i) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',1)) t1 = Thread(target=task,args=('jason',2)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.join() # 主线程等待子线程运行完毕,主线程才运行 print('当前正在活跃的线程数',active_count()) #在等待t1运行结束的时候t已经运行结束了,所以最后只剩下线程本身 print('主')
#结果 1
Thread类的其他方法
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
6.守护线程
无论是进程还是线程,都遵循:守护***会等待主***运行完毕后被销毁。需要强调的是:运行完毕并非终止运行。
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。
(因为主线程的结束也就意味着进程的结束,因为子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了资源也就销毁了,所以要等着非守护线程运行完毕)
7.线程间通信
线程是在一个内存中的,是可以相互通信的
from threading import Thread money = 666 def task(): global money money = 999 t = Thread(target=task) t.start() t.join() #主线程等待子线程运行完毕再运行,修改了全局变量money print(money) #打印的是全局变量的值
8.互斥锁 (和进程互斥锁是一样的,在主线程中设置锁,在需要修改数据的地方加锁)
from threading import Thread,Lock import time n = 100 def task(mutex): global n mutex.acquire() tmp = n time.sleep(0.1) n = tmp - 1 mutex.release() t_list = [] mutex = Lock() for i in range(100): t = Thread(target=task,args=(mutex,)) t.start() t_list.append(t) for t in t_list: t.join() print(n)