多任务-进程/线程/协程
1.多任务的概念
1.1 并行和并发
- 单核cpu实现多任务
- 时间片轮转
- 每个任务执行很短的时间
- 假的多任务
- 并发
![image-20210316235023692](/Users/huhao/Library/Application Support/typora-user-images/image-20210316235023692.png)
- 多核cpu实现多任务
- 当运行的程序小于cpu核数
- 不用再去轮转执行
- 这就是并行
![image-20210316235227274](/Users/huhao/Library/Application Support/typora-user-images/image-20210316235227274.png)
- 一般情况下都是并发
2.多任务之线程
- 多线程是实现多任务比较轻便的方式
2.1 多线程的使用步骤
- 函数的执行
import threading
def test1(g_nums):
print('test1---xxxxx{0}'.format(g_nums))
def test2():
print('test2---xxxxx')
def main():
g_nums = [1,2,3]
t1 = threading.Thread(target=test1,args=(g_nums,)) # 通过nums传递参数
t2 = threading.Thread(target=test2)
t1.start() # 线程t1开始执行
t2.start() # 线程t2开始执行
print(threading.enumerate()) # 查看进程数量
if __name__ == "__main__":
main()
- 类的执行
# 通过继承threading.Thread和重写run()方法可以实现类的线程执行,其原理就是继承
import threading
import time
class Mythread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
print('myThread----{0}'.format(i))
if __name__ == "__main__":
t = Mythread()
t.start
- target指定执行什么函数
- args表示传递什么参数 是一个元组
2.2 多线程的创建和死亡
- 线程的创建
- 调用线程threading.Thread对象是创建一个对象
- 只有开始threading.Thread.start()方法的时候线程才会创建并且开始运行
- 线程的结束
- 如果threading.Tread指定的函数执行结束,那么这个线程就结束了
- 主线程会默认等待所有子线程执行结束后它才结束
- 如果主线程不小心挂了,子线程也就挂了
2.3 线程之间共享全局变量
- 多任务往往配合使用
- 所以共享全局变量
- 在一个函数中对全局变量修改是否要加global要看全局变量的指向是不是改变
- 如果只是修改了数据,不用加gloabl
- 如果修改了纸箱则要加global
2.4 多线程的问题-共享全局变量的问题
- 多线程共享全局变量而且同时操作全局变量 有时候会出现资源竞争
![image-20210317005813818](/Users/huhao/Library/Application Support/typora-user-images/image-20210317005813818.png)
-
解决方法一:互斥锁
- 利用原子性(要么不执行要么执行完)
- 通过互斥锁来解决这个问题
- 在写操作之前加锁
- 在操作之后释放锁
# 创建锁 metux = threading.lock() # 加锁 metux.acquire() # 释放锁 metux.release()
![image-20210317010822185](/Users/huhao/Library/Application Support/typora-user-images/image-20210317010822185.png)
-
解决方法二:优化 只在写操作的时候加锁子
![image-20210317011138982](/Users/huhao/Library/Application Support/typora-user-images/image-20210317011138982.png)
2.5 多线程的问题-多个互斥锁的死锁问题
- 你等我,我等你就会出现死锁的问题
- 如何避免死锁
- 添加超时时间
- 从程序的角度避免多个人写-银行家算法
3.多任务之进程
3.1 程序和进程
- 程序:程序就是一个没有执行的类似于xxx.exe的东西
- 进程:程序的代码+分配的资源就是进程
3.2 进程的状态
-
新建
-
就绪
-
运行
-
死亡
-
等待
![image-20210317131843032](/Users/huhao/Library/Application Support/typora-user-images/image-20210317131843032.png)
3.3 多进程的使用步骤
# import threading
import multiprocessing
def test1():
print('test1---xxxxx')
def test2():
print('test2---xxxxx')
def main():
p1 = multiprocessing.Process(target=test1)
p2 = multiprocessing.Process(target=test2)
p1.start()
p2.start()
if __name__ == "__main__":
main()
3.4 多进程的问题
![image-20210317132749018](/Users/huhao/Library/Application Support/typora-user-images/image-20210317132749018.png)
- 多进程相当于代码copy多份+占用资源共同执行。所以进程的耗费资源很大。
- copy的信息中还有类似pid这样的信息是不一样的
- python多进程具有c语言写时拷贝的特点 只有修改的时候才会去copy
3.5 进程之间的通信
3.5.1 socket
3.5.2 queue
- 简单的队列可以在同台机器上实现进程之间的通信
- redis及其他高级队列可以跨机器
![image-20210317190019772](/Users/huhao/Library/Application Support/typora-user-images/image-20210317190019772.png)
3.5.3 进程池
-
相当于先创建好进程 相当于mysql连接池
-
多个进程的时候用进程池
-
进程池可以重复利用进程池中的进程
-
可以创建几个进程要和计算机硬件相关
![image-20210317190438274](/Users/huhao/Library/Application Support/typora-user-images/image-20210317190438274.png)
4.进程和线程的对比
-
代码-->进程
-
进程时一坨资源和代码的总和
- qq多开时多进程
- 进程之中实现多任务的还是线程
-
线程
- 一个qq中的多个功能是线程
- 一个进程中的多个任务是线程
5.协程
5.1 迭代器
![image-20210318183103647](/Users/huhao/Library/Application Support/typora-user-images/image-20210318183103647.png)
![image-20210318183305098](/Users/huhao/Library/Application Support/typora-user-images/image-20210318183305098.png)
5.1.1 可迭代对象
-
可迭代对象
- 列表、元祖、字典、集合、字符串
- 如果一个对象要是可迭代对象,那它必须有
__iter__
方法
from collections import Iterable isinstance([11,22,33],Iterable) # True isinstance((11,22,33),Iterable) # True isinstance({'a':11,'b':22,'c':33},Iterable) # True isinstance('112233',Iterable) # True
5.1.2 迭代器
![image-20210318183404142](/Users/huhao/Library/Application Support/typora-user-images/image-20210318183404142.png)
- 迭代器
- 如果一个对象实现了
__iter__
和__next__
方法,那它就是一个迭代器
- 如果一个对象实现了
5.1.3 迭代器的应用
-
要保存数据的两种方式
- 先创造并保存数据(数据)--- 鱼
- 先生成一个迭代器(创造数据的工具)---- 鱼竿
-
斐波那契数列
- 第一种--列表
nums = [] a = 0 b = 1 i = 0 while i<10: nums.append(a) a,b = b,a+b i += 1 for num in nums: print(num)
- 第二种--迭代器
class Fibonacci(object): def __init__(self,all_num): self.all_num = all_num self.current_num = 0 self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): if self.current_num < self.all_num: ret = self.a self.a,self.b = self.b ,self.a+self.b self.current_num += 1 return ret else: raise StopIteration fibo = Fibonacci(10) for num in fibo: print(num)
5.2 生成器
-
生成器是特殊的迭代器
-
生成器的实现方式一:
[x*2 for x in range(10)] # 迭代器 (x*2 for x in range(10)) # 生成器
- 函数汇总有yeild自动变成生成器
- 只要有yeild就是生成器
- 函数汇总有yeild自动变成生成器
-
生成器的实现方式二:
def create_num(all_num): a, b =0, 1 current_num = 0 while current_num < all_num: yield a a, b = b, a+b current_num += 1 obj = create_num(10) res = next(obj) print(res)
![image-20210318214702428](/Users/huhao/Library/Application Support/typora-user-images/image-20210318214702428.png)
5.3 协程-yield
- 通过yield实现协程-多任务
import time
def task_1():
while True:
print('-----1-----')
time.sleep(1)
yield # 函数中有yield相当于变成了一个生成器
def task_2():
while True:
print('-----2-----')
time.sleep(1)
yield # 函数中有yield相当于变成了一个生成器
def main():
t1 = task_1() #
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
5.4 协程-greenlet
- greenlet替换yield
sudo pip3 install greenlet
from greenlet import greenlet
import time
def task_1():
while True:
print('-----1-----')
gr2.switch() # 相当于切换到task_2去执行
time.sleep(1)
def task_2():
while True:
print('-----2-----')
gr1.switch() # 相当于切换到task_1去执行
time.sleep(1)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
5.5 协程-gevent
- gevent实现协程并发
sudo pip3 install gevent
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
- gevent的特点
- 遇到延时操作gevent就会切换任务
- gevent.sleep(0.1)
- gevent封装了greenlet
- greenlet封装了yield
- 遇到延时操作gevent就会切换任务
- 给代码打补丁
import gevent
from gevent import monkey
monkey.patch_all() # 把程序中需要pending的代码自动换成gevent中的代码
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
6.进程、线程、协程对比
-
进程是资源分配的最小单位
- 代码+操作系统的资源
-
线程是操作系统调度的最小单位
-
进程之间的切需要很大的资源,效率低
-
线程切换需要的资源一般,效率一般
-
协程切换任务资源很小、效率更高
-
进程和线程根据cpu核心数和任务的数量,可能是并行也可能是并发、协程一定是并发、
进程 | 线程 | 协程 | |
---|---|---|---|
依赖关系 | 依赖于进程 | 依赖于线程 | |
资源消耗 | 消耗最大 | 消耗次之 | 消耗最小 |