一 多进程
"""
python并发编程之多进程
"""
"""
串行:
并行:指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
并发:并发当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,
再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。
简而言之,看起来像同时运行的
描述任务的提交方式:
同步:同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。
异步:异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。
描述程序的运行状态:
阻塞:调用者在调用这个方法之后不继续执行,直到这个调用结果出来
非阻塞:调用者在调用这个方法之后继续执行,不管此调用
"""
# multiprocessing # 用来开启子进程,并在子进程中执行我们定制的任务
# 强调:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内
# 创建并开启子进程的两种方式
# 1
# import time
# import random
# from multiprocessing import Process
# def piao(name):
# print("start...", name)
# time.sleep(random.randrange(1, 5))
# print("end...", name)
# p1 = Process(target=piao, args = ("egon",)) # args元组形式
# p2 = Process(target=piao, args = ("alex",))
# p3 = Process(target=piao, args = ("lxx", ))
# if __name__ == '__main__': # windows必须将进程的开始放在main函数中
# p1.start()
# p2.start()
# p3.start()
# print("主线程")
# 2
# import time
# import random
# from multiprocessing import Process
# class Piao(Process):
# def __init__(self, name):
# super(Piao, self).__init__()
# self.name = name
# def run(self): # 必须run方法
# print("start...", self.name)
# time.sleep(random.randrange(1, 5))
# print("end...", self.name)
# p1 = Piao("egon")
# p2 = Piao("alex")
# if __name__ == '__main__':
# p1.start()
# p2.start()
# 进程的内存空间是隔离开的
# from multiprocessing import Process
# n = 100
# def work():
# global n
# n = 0
# print("子进程内: ", n) # 0
# if __name__ == '__main__':
# p = Process(target=work)
# p.start()
# print("主进程内: ", n) # 100
"""
Process对象的join方法
join:主进程等待子进程结束, 主进程在等
"""
# from multiprocessing import Process
# import time
# import random
# class Piao(Process):
# def __init__(self, name):
# super().__init__()
# self.name = name
# def run(self):
# print("%s is start" % self.name)
# time.sleep(random.randrange(1, 5))
# print("%s is end")
# p = Piao("egg")
# if __name__ == '__main__':
# p.start()
# p.join(0.001)
# print("开始")
from multiprocessing import Process
import time
#
# def task(name, n):
# print(f"{name} run")
# time.sleep(n)
# print(f"{name} end")
# p1 = Process(target=task, args=("egg", 1))
# p2 = Process(target=task, args=("lxx", 2))
# p3 = Process(target=task, args=("hrr", 3))
# if __name__ == '__main__':
# start_time = time.time()
# p1.start()
# p2.start()
# p3.start()
# p1.join()
# p2.join()
# p3.join()
# print("主线程", time.time() - start_time)
# for 循环处理上述注释的代码
# if __name__ == '__main__':
# start_time = time.time()
# p_list = []
# for i in range(1, 4):
# p = Process(target=task, args=(f"子进程{i}:", i))
# p.start()
# # p.join() # 不能添加到这里,这会导致第一个start运行后就会运行join,变成串行
# p_list.append(p)
# for p in p_list:
# p.join()
# print("主:", time.time() - start_time)
# -------------------------------------------------------------------------------------------------------
# Process对象的其他方法或属性 terminate is_alive
# from multiprocessing import Process
# import time
# import random
# class Piao(Process):
# def __init__(self, name):
# super(Piao, self).__init__()
# self.name = name
# def run(self):
# print(f"{self.name} is start")
# time.sleep(random.randrange(1, 5))
# print(f"{self.name} is end")
# p1 = Piao("egg")
# if __name__ == '__main__':
# p1.start()
# p1.terminate() # 关闭进程,不会立即关闭,所以is_alive立刻查看结果可能还存活
# print(p1.is_alive()) # True
# print("开始")
# time.sleep(1)
# print(p1.is_alive()) # False
# # name与pid
# from multiprocessing import Process
# import time
# import random
# class Piao(Process):
# def __init__(self, name):
# super().__init__()
# self.name = name
# def run(self):
# print(f"{self.name} start")
# time.sleep(random.randrange(1, 3))
# print(f"{self.name} end")
# p = Piao("egg")
# if __name__ == '__main__':
# p.start()
# print("开始")
# print(p.pid)
# 查看当前进程号
# from multiprocessing import Process,current_process
# import time
# import os
# def task():
# print(f"当前子进程号: {current_process().pid}")
# print(f"当前子进程号: {os.getpid()}") # 同上,查看当前进程号
# time.sleep(50)
#
#
# if __name__ == '__main__':
# p = Process(target=task)
# p.start()
# p.terminate() # 杀死当前进程,告诉操作系统去帮你终止当前进程,但是需要一定时间
# time.sleep(0.0000001)
# print(p.is_alive()) # 判断当前进程是否存活
# """
# 一般情况下,我们会默认将存储布尔值的变量名和返回的结果是布尔值的方法名用is_开头
# """
# print("主:", current_process().pid)
# print("主主", os.getppid()) # 父进程的pid号,这个pycharm的进程号
# ----------------------------------------------------------------------------------------------
# 僵尸进程与孤儿进程
"""
一:僵尸进程(有害)
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,
那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。详解如下
我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,
即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。
因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,
退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,
其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程.
此即为僵尸进程的危害,应当避免。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。
这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。
如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
二:孤儿进程(无害)
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。
每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,
当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
"""
# import os
# import sys
# import time
# pid = os.getpid()
# ppid = os.getppid()
# print(f"pid:{pid},ppid:{ppid}")
# pid = os.fork()
# # 执行pid=os.fork()则会生成一个子进程
# # 返回值pid有两种值:
# # 0,表示在子进程中
# # >0,表示在父进程中
# if pid > 0:
# print("父进程中")
# sys.exit(0)
# time.sleep(1) # 保证主线程退出完毕
# print(os.getpid(), os.getppid())
# ------------------------------------------------------------------------------------------
# # 守护进程
# from multiprocessing import Process
# import time
# import random
# class Piao(Process):
# def __init__(self, name):
# super(Piao, self).__init__()
# self.name = name
# def run(self):
# print(f"{self.name} starting")
# time.sleep(random.randrange(1, 3))
# print(f"{self.name} end")
# p = Piao("egg")
# p.daemon = True # 一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
# if __name__ == '__main__':
# p.start()
# print("主")
#-----------------------------------------------------------------------------------
"""
互斥锁
问题:
多个进程操作同一份数据的时候,会出现数据错乱的问题
方案:
针对上述问题,解决方式就是加锁处理:将并发变成串行,牺牲效率但是保证了数据的安全
"""
# from multiprocessing import Process, Lock
# import time
# import random
# import json
# import os
# # 查票
# def search(i):
# with open("ticket.json", mode="r", encoding="utf-8") as f:
# dic = json.load(f) # 把json-->dic
# print(f"用户{i}查询余票%s" % dic.get("ticket_num"))
# # 买票,先查询再买票
# def buy(i):
# # 查询
# path = os.path.dirname(__file__) # 当前目录 D:/pyCharm2020/me/面向对象高级/08并发编程
# path_ticket = os.path.join(path, "ticket.json")
# with open(path_ticket, mode="r", encoding="utf-8") as f:
# dic = json.load(f) # json --> dic
# time.sleep(random.randint(1, 3)) # 模拟网络延迟
# # 买票
# if dic.get("ticket_num") > 0: # 判断票数
# # 修改数据
# dic["ticket_num"] -= 1
# # 保存数据
# with open("ticket.json", mode="w", encoding="utf-8") as f:
# json.dump(dic, f)
# print(f"用户{i}购票成功")
# else:
# print(f"用户{i}购票失败")
# def run(i, mutex):
# search(i) # 查票过程不需要抢锁
# # 抢锁
# mutex.acquire()
# buy(i)
# # 释放锁
# mutex.release()
# if __name__ == '__main__':
# mutex = Lock() # 创建互斥锁
# for i in range(1, 11):
# p = Process(target=run, args=(i, mutex))
# p.start()
"""
扩展: 行锁,表锁
注意:
1 锁不要轻易使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
2 锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可)
"""
# ------------------------------------------------------------------------------------------------------
# """
# 队列:先进先出
# 堆栈:先进后出
# """
# from multiprocessing import Queue
# # 创建一个队列,括号内的数字,为最大接收进程数
# q = Queue(5)
# # 往队列中存数据
# q.put(111)
# q.put(222)
# q.put(333)
# # print(q.full()) # 判断当前队列是否满了 --- 没满:False
# q.put(444)
# q.put(555)
# # q.put(666) # 当队列数据放满之后,程序会阻塞,直到有位置让出来,不会报错
# # 去队列中取数据
# v1 = q.get()
# v2 = q.get()
# v3 = q.get()
# # print(q.empty()) # 判断当前队列是否空 -- 没空:False
# v4 = q.get()
# v5 = q.get()
# # v6 = q.get() # 队列中如果没有数据的话,get方法会原地阻塞
# # v6 = q.get_nowait() # 队列中没有数据直接报错queue.Empty
# try:
# v6 = q.get(timeout=3) # 队列中没有数据,等待3秒然后报错queue.Empty
# except Exception as e:
# print("空了")
# print(v1,v2,v3,v4,v5)
# """
# q.full()
# q.empty()
# q.get_nowait()
# 在多进程的情况下是不精确的,一个进程刚判断完,另一个进程进行了操作
# """
# ---------------------------------------------------------------------------------------------------------
# IPC机制
# 进程之间的相互通信
# from multiprocessing import Process
# from multiprocessing import Queue
# def producers(q):
# q.put("hello python") # 为队列传入数据
# # print("this is producers")
# def consumer(q):
# print(q.get())
# if __name__ == '__main__':
# q = Queue() # 创建一个队列
# p1 = Process(target=producers, args=(q, )) # 创建一个进程
# p2 = Process(target=consumer, args=(q, ))
# p1.start()
# p2.start()
# # print(q.get()) # 取出队列数据
# print("🐖")
# ---------------------------------------------------------------------------------------------------------
# 生产者,消费者模型
from multiprocessing import Process
from multiprocessing import Queue, JoinableQueue
import time
import random
# 生产者
def producers(name, food, q):
for i in range(1, 5):
data = f"{name}制作了{food}{i}"
# 模拟延迟
time.sleep(random.randint(1, 3))
print(data)
# 将数据放入队列中
q.put(data)
def consumer(name, q):
while 1:
data = q.get() # 从队列里面取出数据
# """
# 此处需要判断data是否还存在,当data传入为空时,q.get()会一直卡住
# """
# if data is None:
# break
# 模拟吃的过程
time.sleep(random.randint(1, 3))
print(f"{name}吃了{data}")
q.task_done() # 告诉队列你从里面取出一个数据并且处理完了
if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
"""
JoinableQueue每当你往该队列中存入数据的时候,内部会有一个计数器+1
每当你调用q.task_done()的时候计数器-1
q.join()当计数器为0的时候,才往后运行
"""
p1 = Process(target=producers, args=("egg", "包子", q))
p2 = Process(target=producers, args=("lxx", "面条", q))
c1 = Process(target=consumer, args=("alex", q))
c2 = Process(target=consumer, args=("lqq", q))
p1.start()
p2.start()
c1.daemon = True
c2.daemon = True
c1.start()
c2.start()
p1.join()
p2.join()
# 等待生产者生产完毕后,传一个特定值
# q.put(None) # 传入None,这个None一定会在put所有参数之后,因为生产者已经生产完毕了。
# q.put(None) # 有两个消费者,所以需要传入两次None
q.join() # 等待队列中所有的代码被取完,然后再往下执行代码
二 多线程
"""
线程
"""
"""
什么是线程:
进程:资源单位(创建一个进程仅仅只是在内存空间中开辟一个独立的空间)
线程:执行单位(真正被CPU执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中
所需要使用到的资源都找所在的进程索要)
将操作系统比喻成一个大的工厂,进程就相当于工厂里面的车间,而线程就是车间里面的流水线
进程和线程都是虚拟单位,只是为了我们更加方便的描述问题
为何要有线程:
开设进程:
1,申请内存空间 耗资源
2,”拷贝代码” 耗资源
开设线程:
一个进程内可以开设多个线程,在同一个进程内开设多个线程,无需再次申请内存空间
同一个进程下的多个线程数据是共享的
如何使用:
"""
# 开启线程的两种方法
# 方法一
# from multiprocessing import Process
# from threading import Thread
# import random
# import time
# def task(name):
# print(f"{name} is start")
# time.sleep(random.randint(1, 2))
# print(f"{name} is end")
# if __name__ == '__main__':
# t = Thread(target=task, args=("lxx", ))
# t.start()
# print("主")
# 方法二
# from threading import Thread
# import time
# class MyThread(Thread):
# def __init__(self, name):
# super().__init__() # 重写了别人方法,但是又不知道别人方法里面没有啥,就调用父类的方法
# self.name = name
# def run(self):
# print(f"{self.name} is start")
# time.sleep(1)
# print(f"{self.name} is end")
#
# if __name__ == '__main__':
# t = MyThread("egon")
# t.start()
# print("主")
# ------------------------------------------------------------------------------------------------------
# # 线程的join方法
# from threading import Thread
# import time
# def task(name):
# print(f"{name} is start")
# time.sleep(2)
# print(f"{name} is end")
#
# if __name__ == '__main__':
# t = Thread(target=task, args=("edd", ))
# t.start()
# t.join() # 主线程等待子线程结束之后再结束。主线程在等待
# print("主")
# 同一线程下数据共享
# from threading import Thread
# money = 100
# def task():
# global money
# money = 112
# print(money) # 112
# if __name__ == '__main__':
# t = Thread(target=task)
# t.start()
# t.join()
# print(money) # 112
# print("主")
# 线程对象的其他方法和属性
# from multiprocessing import Process
# from threading import Thread, active_count, current_thread
# import os
# # def task1():
# # print("aaaaa", os.getpid()) # 子进程和父进程不是同一个进程号
# # print("aaaaa", os.getppid())
#
#
# def task2():
# # print("bbbbb", os.getpid()) # 子线程和父进程同一个进程号
# # print("bbbbb", os.getppid())
# print("hello", current_thread().name) # 获取当前线程的名字
#
# if __name__ == '__main__':
# # p = Process(target=task1)
# # p.start()
# # p.join(0.01)
# t = Thread(target=task2)
# t.start()
# t.join(0.01)
# # print("主", os.getpid())
# print("主", active_count()) # 获取当前活线程的个数
# print("主", current_thread().name) # 获取当前线程的名字
# ---------------------------------------------------------------------------------------
# # 守护线程
# from threading import Thread
# import time
# def task():
# print("start")
# time.sleep(2)
# print("end")
# if __name__ == '__main__':
# t = Thread(target=task)
# t.daemon = True # 守护线程
# t.start()
# print("主")
# """
# 主线程运行结束之后不会立即结束,会等待所有其他 非守护线程 结束才会结束,
# 因为主线程的结束意味着所在的进程的结束
# """
# ---------------------------------------------------------------------------------------
# # 线程互斥锁
# # 当多个人同时操作同一份数据的时候,数据不安全,此时就该考虑加锁处理
# from threading import Thread, Lock
# import time
# num = 100
# mutex = Lock()
# def task():
# global num
# mutex.acquire() # 在进行操作数据的时候,进行加锁处理
# temp = num
# time.sleep(0.1)
# num = temp - 1
# mutex.release() # 释放锁
# if __name__ == '__main__':
# # 模拟100个线程
# t_list = [] # 生成的子线程列表,是为了把所有的子线程进行.join方法,让所有子线程运行完成之后,再结束主线程,确保每一个数据的安全
# for i in range(100):
# t = Thread(target=task)
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
# print(num) # 99,未加锁处理时,100个线程同时去拿num,然后同时进行-1操作,所以是99。加锁之后结果为0
# ---------------------------------------------------------------------------------------
# GIL:全局解释器锁(冷门知识点,interview)
"""
python解释器有多个版本
Cpython 普遍使用
Jpython
Pypypython
在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行
同一个进程下的多个线程无法利用多核优势!!!
疑问:Python的多线程是不是一点用都没有???无法利用多核优势
因为cpython中的内存管理(垃圾回收机制)不是线程安全的
内存管理:
1 引用计数
2 标记清除
3 分代回收
重点:
1.GIL不是python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一进程下的多个线程无法同时执行
4.针对不同的数据,还是需要加不同的锁处理
5.解释型语言的通病:同一进程下多个线程无法利用多核优势
"""
# ---------------------------------------------------------------------------------------
# GIL与普通互斥锁的区别
"""
GIL是解决线程与线程之间的数据错乱的问题
"""
# -------------------------------------------------------------------------------------------
# 多进程与多线程的比较
# 同一个进程下的多线程无法利用多核优势(因为GIL问题),是不是就没有用了
"""
多线程是否有用要看具体情况
单核:四个任务(IO密集型,计算密集型)
多核:四个任务(IO密集型,计算密集型)
# 计算密集型 每个任务都需要10s
单核(不用考虑了)
多进程:额外的消耗资源
多线程:节省资源
多核
多进程: 总耗时 10s+
多线程: 总耗时 40s+
"""
# # 代码实现:计算密集型 进程与线程差不多
# from multiprocessing import Process
# from threading import Thread
# import time
# import os
#
#
# def work(): # 模拟大量计算
# res = 1
# for i in range(1, 100001):
# res = i * res
#
#
# if __name__ == '__main__':
# l = []
# print(os.cpu_count()) # 查看电脑几核 --> 4
# start_time = time.time()
# for i in range(4):
# # p = Process(target=work)
# t = Thread(target=work)
# # p.start()
# t.start()
# # l.append(p)
# l.append(t)
# for x in l:
# x.join()
# print(time.time() - start_time) # 多进程:9.864635229110718 多线程:35.67854952812195
# 代码实现:IO密集型
from multiprocessing import Process
from threading import Thread
import time
import os
def work(): # 模拟io阻塞
time.sleep(2)
if __name__ == '__main__':
l = []
print(os.cpu_count())
start_time = time.time()
for i in range(400): # 开设400个进程
p = Process(target=work)
p.start()
l.append(p)
# t = Thread(target=work)
# t.start()
# l.append(t)
for x in l:
x.join()
print(time.time() - start_time) # 多进程:11.106568813323975 多线程:2.1207492351531982