多任务
概念:操作系统“同时”执行多个任务
日常中多任务无处不在,比如唱歌跳舞,一切皆对象,python如何对这些行为之间的关系进行建模,这个工具就是多任务。
重要概念
并发与并行
并发:指的是任务数多于cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行,存在着多个任务共用一个核,因为任务切换很快,所以没有感觉,像是在一起执行
并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的
多任务编码
(1)线程
概念:线程是进程中的一个执行任务(控制单元),cpu调度的最小单元
线程版唱歌跳舞
import time
import threading
def sing():#自定义方法,使用target参数
for i in range(5):
print("在唱歌~~%d" % i)
time.sleep(1)
def dance():
for i in range(5):
print("也在跳舞~~%d" % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == "__main__":
main()
import threading
import time
class SingThread(threading.Thread):#自定义继承类,继承Thread
def run(self): # 必须有run方法
for i in range(5):
time.sleep(1)
msg = "在唱歌" + str(i)
# name表示当前线程的名字,str()把i转成string类型才能拼接
print(msg)
class DanceThread(threading.Thread):
def run(self): # 必须有run方法
for i in range(5):
time.sleep(1)
msg = "在跳舞" + str(i)
# name表示当前线程的名字,str()把i转成string类型才能拼接
print(msg)
def main():
t1 = SingThread()
t2 = DanceThread()
t1.start()
t2.start()
if __name__ == '__main__':
main()
#class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
线程数据
通过grobal参数在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全),解决线程非安全的问题要使用锁
#不加锁前,全局变量的计算会出错
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print("----in test1 g_num = %d ----" % g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print("----in test2 g_num = %d ----" % g_num)
def main():
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
# 等待两个线程执行完毕
time.sleep(5)
print("----in main g_num = %d ----" % g_num)
if __name__ == '__main__':
main()如何使用锁
# 创建锁
mutex = threading.Lock()# 或者使用Rlock(),Rlock可以在一个线程中使用多个锁定和释放,就近原则
# 锁定
mutex.acquire()
# 释放
mutex.release()
#加锁后的全局变量计算正确
import threading
import time
g_num = 0
def test1(num):
global g_num
# 上锁,如果之前没有被上锁,此时上锁成功
# 如果上锁之前已经上过锁,那么此时堵塞,直到这个锁被解开
for i in range(num):
mutex.acquire()
g_num += 1
mutex.release()
print("----in test1 g_num = %d ----" % g_num)
def test2(num):
global g_num
for i in range(num):
mutex.acquire()
g_num += 1
mutex.release()
print("----in test2 g_num = %d ----" % g_num)
# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()
def main():
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print("----in main g_num = %d ----" % g_num)
if __name__ == '__main__':
main()
死锁
原因:两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,在无外力作用下会无限期等待下去。
进程
(1)重要概念
概念:一个程序运行起来后,代码+用到的资源 称之为进程,进程是系统调度和资源分配的一个独立最小单元
进程的生理周期:

就绪态:运行的条件都已经满足,正在等在cpu执行
运行态:cpu正在执行其功能
等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态
(2)进程的创建
import multiprocessing
import time
def test1():
for i in range(5):
print("t1:%d" % i)
time.sleep(1)
def test2():
for i in range(5):
print("t2:%d" % i)
time.sleep(1)
def main():
p1 = multiprocessing.Process(target=test1)
p2 = multiprocessing.Process(target=test2)
p1.start()
p2.start()
if __name__ == '__main__':
main()#Process(group=None, target=None, name=None, args=(), kwargs={})
进程常见方法
- start():启动子进程实例(创建子进程)
- is_alive():判断进程子进程是否还在活着
- join([timeout]):是否等待子进程执行结束,或等待多少秒
- terminate():不管任务是否完成,立即终止子进程
进程通信
进程要通信要通过第三方信息队列程序,可以使用multiprocessing模块的Queue,进程的args参数传递的是Queue的对象
import multiprocessing
import time
def sendpro(q):
data = [11, 22, 33]
for temp in data:
q.put(temp)
time.sleep(1)
print("数据存入queue队列中")
def getpro(q):
while True:
temp = q.get()
print(temp)
if q.empty():
break
def main():
# 创建一个queue队列
q = multiprocessing.Queue(3)
p1 = multiprocessing.Process(target=sendpro, args=(q, ))
p2 = multiprocessing.Process(target=getpro, args=(q, ))
p1.start()
p1.join() # 等待子进程结束
p2.start()
if __name__ == '__main__':
main()
进程池
常见方法:
创建进程池:进程池名 = multiprossing.Pool(最大进程数),提示:使用进程池创建的进程是守护主进程的状态,默认自己通过Process创建的进程是:不守护主进程的状态
选择同步或异步执行任务
同步(一个任务执行完后,另一个任务才执行):进程池名.apply(函数名)
异步(任务执行不会等待,多个任务一起执行):进程池名.apply_async(函数名)
from multiprocessing import Pool
import os
import time
import random
def worker(msg):
t_start = time.time()
print("%s开始执行,进程号为%d" % (msg,os.getpid()))
time.sleep(random.random())
t_stop = time.time()
print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))
po = Pool(3) # 定义一个进程池,最大进程数3
for i in range(0,10):
# Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
# 每次循环将会用空闲出来的子进程去调用目标
po.apply_async(worker,(i,))#确定进程池内多个任务异步
print("----start----")
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
协程
概念:多个协程在线程内部执行,共享线程的资源 ,是一个比线程更小的执行单元,能实现多任务
协程在一个线程内,一定是并发的
利用yield做协程
写多个函数,每个函数中都写yield,函数执行时遇到yield就会阻塞,然后交替着调用不同任务的next()方法,这样就用协程实现了多任务
import time
def func1():
while True:
print("-----1-----")
time.sleep(0.1)
yield
def func2():
while True:
print("-----2-----")
time.sleep(0.1)
yield
def main():
t1 = func1()
t2 = func2()
while True:
next(t1)
next(t2)
if __name__ == '__main__':
main()
利用greenlet(pip)做协程,手写代码切换
import time
from greenlet import greenlet
def test1():
while True:
print("----1----")
gl2.switch()
time.sleep(0.1)
def test2():
while True:
print("----2----")
gl1.switch()
time.sleep(0.1)
gl1 = greenlet(test1)
gl2 = greenlet(test2)
gl1.switch()#切换到协程所在函数或启动利用gevent(pip)做协程,io时自动切换
gevent的原理是当一个greenlet遇到 IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到 IO 操作完成,再在适当的时候切换回来继续执行。
import gevent
import time
from gevent import monkey
monkey.patch_all()#使用mokey打补丁,因为使用gevent时,相关的一切都要更换成gevent的耗时,阻塞,这样太麻烦,使用猴子补丁可以不用更换
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
#join的一次性加载版joinall
#spawn(fuc,args,返回一个协程)gevent.joinall([
gevent.spawn(f, 5),
gevent.spawn(f, 5),
gevent.spawn(f, 5),
])
















