文章目录
1、io密集型和计算密集型
# 计算密集型:
from multiprocessing import Process
from threading import Thread
import time
def task():
count = 0
for i in range(100000000):
count += i
if __name__ == '__main__':
start_time = time.time()
list = []
for i in range(10):
t = Thread(target=task) # 开线程耗费时间: 47.8338840007782
t.start()
t = Process(target=task) # 开进程耗费时间: 13.326022386550903
t.start()
list.append(t)
for t in list:
t.join()
print(time.time() - start_time)
# io密集型:
from multiprocessing import Process
from threading import Thread
import time
def task():
time.sleep(2)
if __name__ == '__main__':
start_time = time.time()
list = []
for i in range(1000):
t = Thread(target=task) # 开线程耗费时间: 2.106079339981079
t.start()
t = Process(target=task) # 开进程耗费时间: 37.65490651130676
t.start()
list.append(t)
for t in list:
t.join()
print(time.time() - start_time)
结论:
以下只针对于cpython解释器
在单核情况下:
开多线程还是开多进程?不管干什么都是开线程
在多核情况下:
如果是计算密集型,需要开进程,能被多个cpu调度执行
如果是io密集型,需要开线程,cpu遇到io会切换到其他线程执行
2、Event事件
Event事件: 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
比如一个线程等待另一个线程执行结束再继续执行
from threading import Thread, Event
import time
event = Event()
def light():
print('现在是红灯')
time.sleep(10)
print('绿灯亮了')
event.set() # 告诉等待红灯的人可以走了
def car(n):
print('汽车{}正在等待'.format(n))
event.wait() # 只要信号没来,就卡在这
print('汽车{}冲了出去...'.format(n))
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(1, 11):
t = Thread(target=car, args=(i,))
t.start()
2.1、Event小案例
起两个线程,第一个线程读文件的前半部分,读完发一个信号,另一个线程读后半部分,并打印
from threading import Thread, Event
import os
event = Event()
file_size = os.path.getsize('test.txt')
def read_first():
with open(r'test.txt', mode='rt', encoding='utf-8') as fp:
data = fp.read(file_size // 2)
print(data)
print('前半部分内容读取完毕,发送信号')
event.set()
def read_last():
event.wait() # 等着发信号
with open(r'test.txt', mode='rt', encoding='utf-8') as fp:
fp.seek(file_size // 2, 0)
data = fp.read()
print(data)
if __name__ == '__main__':
t = Thread(target=read_first)
t.start()
t1 = Thread(target=read_last)
t1.start()
test.txt文件内容:
0123456789
运行结果:
01234
前半部分内容读取完毕,发送信号
56789
3、线程Queue
只要你用并发,就会有锁的问题,但是不能一直去自己加锁
那么我们就用QUEUE,这样还解决了自动加锁的问题
由Queue延伸出的一个点也非常重要的概念。以后写程序也会用到
这个思想。就是生产者与消费者问题
from queue import Queue, PriorityQueue, LifoQueue
# Queue: 队列,先进先出
q = Queue(3)
q.put('allen')
q.put_nowait(18)
q.put('male', timeout=1)
print(q.get()) # allen
print(q.get()) # 18
print(q.get_nowait()) # male
print(q.full()) # False
print(q.empty()) # True
# PriorityQueue: 优先级队列,谁的数字小谁先出(数字越小,级别越高,支持负数)
q = PriorityQueue(3)
q.put((100, 'allen'))
q.put_nowait((200, 18))
q.put((-300, 'male'), timeout=1)
print(q.get()) # (-300, 'male')
print(q.get()) # (100, 'allen')
print(q.get_nowait()) # (200, 18)
print(q.full()) # False
print(q.empty()) # True
# LifoQueue: 栈,后进先出
q = LifoQueue(3)
q.put('allen')
q.put_nowait(18)
q.put('male', timeout=1)
print(q.get()) # male
print(q.get()) # 18
print(q.get_nowait()) # allen
print(q.full()) # False
print(q.empty()) # True
注意:
进程queue和线程queue不是同一个,线程间通信,因为共享变量会出现数据不安全问题,用线程queue通信,不需要加锁,内部自带,queue是线程安全的
4、定时器Timer
# 多长时间之后执行一个任务
from threading import Timer
def task(name):
print('任务{}开始执行了'.format(name))
if __name__ == '__main__':
t = Timer(3, task, args=('007',)) # 本质是开了个线程,延迟3秒执行
t.start()
5、线程池
需要注意一下
不能无限的开进程,不能无限的开线程
最常用的就是开进程池,开线程池。其中回调函数非常重要
回调函数其实可以作为一种编程思想,谁好了谁就去调用
5.1、concurrent.futures
1、concurent.future模块是用来创建并行的任务,提供了更高级别的接口,
为了异步执行调用
2、concurent.future这个模块用起来非常方便,它的接口也封装的非常简单
3、concurent.future模块既可以实现进程池,也可以实现线程池
4、模块导入进程池和线程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
还可以导入一个Executor,但是你别这样导,这个类是一个抽象类
抽象类的目的是规范他的子类必须有某种方法(并且抽象类的方法必须实现),但是抽象类不能被实例化
5、p = ProcessPoolExecutor(max_works)对于进程池如果不写max_works:默认的是cpu的数目,默认是4个
p = ThreadPoolExecutor(max_works)对于线程池如果不写max_works:默认的是cpu的数目*5
6、如果是进程池,得到的结果如果是一个对象。我们得用一个.get()方法得到结果
但是现在用了concurent.future模块,我们可以用obj.result方法
p.submit(task,i) # 相当于apply_async异步方法
p.shutdown() # 默认有个参数wite=True (相当于close和join)
5.2、开线程池的两种方式
5.2.1、方式一
5.2.1.1、同步方式
from concurrent.futures import ThreadPoolExecutor
import time, random
pool = ThreadPoolExecutor(5)
def task(n):
print('任务{}开始了'.format(n))
time.sleep(random.randint(1, 3))
print('任务{}结束了'.format(n))
return '任务{}'.format(n)
if __name__ == '__main__':
for i in range(20):
obj = pool.submit(task, i).result()
print(obj)
pool.shutdown()
5.2.1.2、异步方式
from concurrent.futures import ThreadPoolExecutor
import time, random
pool = ThreadPoolExecutor(5)
def task(n):
print('任务{}开始了'.format(n))
time.sleep(random.randint(1, 3))
print('任务{}结束了'.format(n))
return '任务{}'.format(n)
if __name__ == '__main__':
list = []
for i in range(20):
res = pool.submit(task, i)
list.append(res)
for x in list:
print(x.result())
pool.shutdown()
5.2.2、方式二(推荐写法)
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time, random
pool = ThreadPoolExecutor(5)
def task(name):
print('任务{}开始了'.format(name))
time.sleep(random.randint(1, 3))
print('任务{}结束了'.format(name))
return '任务{}'.format(name)
def call_back(f):
print(f.result())
if __name__ == '__main__':
for i in range(20):
pool.submit(task, i).add_done_callback(call_back)
5.2.3、利用线程池获取网址页面
from concurrent.futures import ThreadPoolExecutor
import requests
pool = ThreadPoolExecutor(2)
def get_page(url):
res = requests.get(url) # 向这个地址发送请求
name = url.rsplit('/')[-1] + '.html'
print(name)
# res.content拿到页面的二进制
return {'name': name, 'data': res.content}
def call_back(f):
dic = f.result()
with open(r'{}'.format(dic.get('name')), mode='wb') as fp:
fp.write(dic.get('data'))
if __name__ == '__main__':
url_lists = ['https://www.baidu.com', 'https://www.sina.com.cn', 'https://www.qq.com']
for url in url_lists:
pool.submit(get_page, url).add_done_callback(call_back)
6、进程池
# 只是在导入ProcessPoolExecutor有区别,别的都一样
from concurrent.futures import ProcessPoolExecutor
import time, random
pool = ProcessPoolExecutor(5)
def task(name):
print('任务{}开始了'.format(name))
time.sleep(random.randint(1, 3))
print('任务{}结束了'.format(name))
return '任务{}'.format(name)
def call_back(f):
print(f.result())
if __name__ == '__main__':
for i in range(20):
pool.submit(task, i).add_done_callback(call_back)
7、线程池和进程池的shutdown
# 主线程等待所有任务执行完成
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor(2)
def task(name):
print('任务{}开始执行'.format(name))
time.sleep(1)
print('任务{}执行结束'.format(name))
if __name__ == '__main__':
for i in range(10):
pool.submit(task, i)
# 放到for外面
pool.shutdown() # 等待所有任务完成,并且把池关闭
pool.submit(task, 'aaa') # 池关闭后任务无法提交,报错
print('主') # 10个线程执行完毕后执行