Python中的多线程
即在一个进程中启动多个线程执行任务。一般来说使用多线程可以达到并行的目的,但由于Python中使用了全局解释锁GIL的概念,导致Python中的多线程并不是并行执行,而是“交替执行
所以Python中的多线程适合IO密集型任务,而不适合计算密集型任务。
Python提供两组多线程接口,一是thread模块_thread,提供低等级接口。二是threading模块,提供更容易使用的基于对象的接口,可以继承Thread对象来实现线程,此外其还提供了其它线程相关的对象,例如Timer,Lock等。
重写run方法
import threading
import time
class MyThread(threading.Thread):
def run(self):
thread_id = threading.get_ident()
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print("线程ID:{},当前时间:{}".format(thread_id, current_time))
if __name__ == "__main__":
for i in range(5):
t = MyThread()
t.start()
多线程实例
import time
import random
from threading import Thread
def down_load(fine_name):
print(f'开始下载{fine_name}...')
start_time = time.time()
second = random.randint(5,10)
time.sleep(second)
end_time = time.time()
Total_time = end_time - start_time
print(f'下载完成,耗时为:{Total_time:.2f}/s')
def main():
zong_start_time = time.time()
a = 'python 从入门到实践'
b = 'JAVA从入门到实践'
t1 = Thread(target=down_load,args=(a,)) #线程1
t1.start()
t2 = Thread(target=down_load,args=(b,)) #线程2
t2.start()
t1.join()
t2.join()
zong_end_time = time.time()
print(f'总计耗时{zong_end_time - zong_start_time:.2f}/s')
if __name__ == '__main__':
main()
线程池 concurrent.futures
import time
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
def work():
print('work')
time.sleep(1)
task = ThreadPoolExecutor(5)
t = []
for n in range(10):
t.append(task.submit(work))
wait(t, return_when=ALL_COMPLETED) # 等待所有子线程执行完成后,继续执行主线程
# wait(t, return_when=FIRST_COMPLETED) # 第一个任务完成后执行主线程
print(1111)
获取返回值
同步
# coding: utf-8
import threading
import time
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, as_completed, FIRST_COMPLETED
def work():
print('work')
time.sleep(1)
task = ThreadPoolExecutor(5)
for n in range(10):
result = task.submit(work)
print(result.result()) # 这种方法和同步没啥区别,
异步
# coding: utf-8
import threading
import time
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, as_completed, FIRST_COMPLETED
def work():
print('work')
time.sleep(1)
task = ThreadPoolExecutor(5)
t = []
for n in range(10):
t.append(task.submit(work))
for i in as_completed(t):
r = i.result()
print(r)
print(1111)
Python中的多进程
由于Python中GIL的原因,对于计算密集型任务,Python下比较好的并行方式是使用多进程,这样可以非常有效的使用CPU资源。当然同一时间执行的进程数量取决你电脑的CPU核心数。
Python中的进程模块为mutliprocess模块,提供了很多容易使用的基于对象的接口。另外它提供了封装好的管道和队列,可以方便的在进程间传递消息。Python还提供了进程池Pool对象,可以方便的管理和控制线程。
"""
创建和使用线程
进程:操作系统分配内存的基本单元,每个进程的内存空间是独立的,进程之间不共享内存
线程:操作系统分配CPU的基本单元,如果程序使用了多线程会比单线程占用更多的CPU资源
一个程序通常是一个或多个进程,一个进程可以包含一个或多个线程
可以通过Windows的任务管理器或macOS系统的活动监视器来查看进程
图形图像处理、音视频压缩解码、科学计算 --->计算密集型任务 ---> 多进程
联网获取数据、文件读写 ---> I/O密集型任务 ---> 多线程 / 异步编程
Python ---> CPython ---> GIL(全局解释器锁)---> 无法发挥多核多CPU优势
"""
import random
import time
from multiprocessing import Process
def upload(filename):
print(f'开始上传{filename}')
time.sleep(random.randint(3, 8))
print(f'{filename}上传完成')
def download(filename):
print(f'开始下载{filename}')
time.sleep(random.randint(5, 10))
print(f'{filename}下载完成')
def main():
start = time.time()
t1 = Process(target=download, args=('Python从入门到精通', )) #进程1
t1.start()
t2 = Process(target=download, args=('深入浅出MySQL', )) #进程2
t2.start()
t3 = Process(target=upload, args=('Python网络爬虫开发', )) #进程3
t3.start()
t1.join()
t2.join()
t3.join()
end = time.time()
print(f'总共耗时: {end - start:.3f}秒')
if __name__ == '__main__':
main()
重写run
import os
import time
from multiprocessing import Process
class MyProcess(Process):
def run(self):
process_id = os.getpid()
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print("进程ID:{},当前时间:{}".format(process_id, current_time))
if __name__ == "__main__":
processes = []
for i in range(5):
p = MyProcess()
processes.append(p)
p.start()
for p in processes:
p.join()
进程池
from multiprocessing import Pool
import os,time,random
def worker(msg):
t_start = time.time()
print("%s开始执行,进程号为%d"%(msg,os.getpid()))
#random.random()随机生成0~1之间的浮点数
time.sleep(random.random()*2)
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-----")
multiprocessing.Pool常用函数解析:
- apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
- apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
- close():关闭Pool,使其不再接受新的任务;
- terminate():不管任务是否完成,立即终止;
- join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;
案例
如果频繁去创建一个进程,然后销毁它,会导致性能下降。对于这种情况推荐的做法是事先创建一个进程池,在有任务达到时从进程池中取出一个进程来执行相关任务,在任务完成后便归还回去。这样做可以复用部分已有进程资源,达到提升效率的作用。
在 mulitprocessing 模块中有一个 Pool 类可以帮助我们完成该任务。下面是一个使用进程池的例子。
import multiprocessing # 引入multiprocessing模块
import time # 引入time模块
def child_process_entry(): # 定义进程入口函数
print(u"子进程在运行")
time.sleep(10)
print(u"子进程结束")
pool_obj = multiprocessing.Pool(processes = 5) # 建立5个元素池子
for i in range(10): # 添加10个进程
pool_obj.apply_async(child_process_entry)
# pool_obj.apply(child_process_entry, args=(i, )) # 同步
pool_obj.close() # 停止添加进程了
pool_obj.join() # 等待进程都结束
进程通信
在一些复杂的应用场景,则希望进程之间有更多的信息交换。下面介绍 multiprocessing 模块提供的进程之间通信的方式,包括管道、队列和锁。
- 管道
管道有两头,一般一头给进程 A,一头给进程 B。如果进程 A 对管道进行写入操作,那么进程B就可以通过读操作看到写入的数据。而且管道是双向的,可以进程 A 写进程 B 读,也可以进程 B 写进程 A 读。
import multiprocessing # 引入multiprocessing模块
def process_A(pipe): # 进程A的入口函数
print(u"进程A发送数据hello到B") # 发送数据hello到进程B
pipe.send('hello')
print(u"进程A等待进程B的输入")
data = pipe.recv() # 结束进程B的数据
print(u'进程A收到B的数据%s' % data)
print(u'进程A结束')
def process_B(pipe): # 进程B的入口函数
print(u'进程B等待进程A的数据')
data = pipe.recv() # 接收数据
print(u'进程B收到B的数据%s' % data)
print(u'进程B发送hi到进程A')
pipe.send('hi') # 发送数据
print(u'进程B结束')
pipe = multiprocessing.Pipe() # 建立管道
# 创建进程A
p1 = multiprocessing.Process(target=process_A, args=(pipe[0],))
# 创建进程B
p2 = multiprocessing.Process(target=process_B, args=(pipe[1],))
p1.start() # 启动进程
p2.start()
p1.join() # 等待进程结束
p2.join()
案例2
import multiprocessing
import random
import time, os
def proc_send(pipe, urls):
for url in urls:
print("Process (%s) send: %s" % (os.getpid(), url))
pipe.send(url)
time.sleep(random.random())
def proc_recv(pipe):
while True:
print("Process (%s) rev: %s" % (os.getpid(), pipe.recv()))
time.sleep(random.random())
if __name__ == '__main__':
pipe = multiprocessing.Pipe()
p1 = multiprocessing.Process(target=proc_send, args=(pipe[0], ['url_' + str(i) for i in range(10)]))
p2 = multiprocessing.Process(target=proc_recv, args=(pipe[1],))
p1.start()
p2.start()
p1.join()
p2.join()
- 队列
对于队列有两个操作,一个是写入数据,一个是读出数据。最简单的使用方式是让一个进程往队列中写入数据,让另外一个进程从队列中读出数据。
import multiprocessing
import time
def process_read(queue): # 读队列子进程
print(u"queue读出进程开始运行")
data = queue.get()
while(data != "quit"): # 一直读,直到读出了quit
print("queue读出进程读出数据%s" % data)
data = queue.get()
print(u'queue读出进程退出')
def process_write(queue): # 写队列子进程
print(u"queue写入进程开始运行")
data = ['good', 'morning', 'everyone'] # 写入的数据
for w in data:
print("queue写入进程写入数据%s" % w)
data = queue.put(w)
time.sleep(1)
data = queue.put('quit') # 发送quit,让读子进程退出
print(u'queue写入进程退出')
queue_obj = multiprocessing.Queue(3)
# 创建两个子进程,一个读队列,一个写队列
p1 = multiprocessing.Process(target=process_read, args=(queue_obj,))
# Pass the other end of the pipe to process 2
p2 = multiprocessing.Process(target=process_write, args=(queue_obj,))
p1.start() # 启动进程
p2.start()
p1.join() # 等待子进程退出
p2.join()
queue_obj.close() # 销毁队列
Lock锁
该对象主要提供了两个接口函数,一个是得到锁 acquire(),另外一个是释放锁 release()。下面的例子演示了 3 个进程,它们都往同一个文件写入数据。由于其每轮操作需要执行 3 次数据写入,而且要求在这 3 次操作之间其他的进程不能往文件写入数据。
import multiprocessing
import time
def process_entry (lock, fd, id): # 进程入口函数
for x in range(30): # 30轮输出,每轮进行3次写操作
lock.acquire() # 得到锁
line = "%d: line 1, round: %d\n" % (id, x)
time.sleep(0.1)
fd.write(line) # 第1次写
fd.flush()
line = "%d: line 2, round: %d\n" % (id, x)
fd.write(line) # 第2次写
fd.flush()
time.sleep(0.1)
line = "%d: line 3, round: %d\n" % (id, x)
fd.write(line) # 第3次写
fd.flush()
lock.release() # 释放锁
time.sleep(0.1)
if __name__ == "__main__":
file_name = "shared_input2.txt"
fd = open(file_name, "a+")
lock = multiprocessing.Lock() # 创建锁
p1 = multiprocessing.Process(target=process_entry, args=(lock, fd, 1))
p2 = multiprocessing.Process(target=process_entry, args=(lock, fd, 2))
p3 = multiprocessing.Process(target=process_entry, args=(lock, fd, 3))
p1.start() # 启动进程
p2.start()
p3.start()
p1.join() # 等待子进程结束
p2.join()
p3.join()
fd.close() # 关闭文件
print(u"开始检查结果") # 开始检查结果
fd2 = open(file_name, "r")
lines = fd2.readlines()
fd2.close()
line_num = len(lines) # 显示行数
print(u"总的行数: %d" % line_num)
# 要求每连续的3行是同一个进程打印出来的
for x in range(int(line_num/3)):
# 第1行和第2行不是同一个进程打印出来的
if lines[x*3][:2] != lines[x*3+1][:2]:
print("line %d: Error" % x*3+1) # 发现错误,退出
sys.exit(1)
# 第1行和第3行不是同一个进程打印出来的
if lines[x*3][:2] != lines[x*3+2][:2]:
print("line %d: Error" % x*3+2) # 发现错误,退出
sys.exit(1)
print(u"成功通过检查")
也可以使用 with lock 语句,这样就不用再显式调用 acquire() 和 release() 了,只需将 acquire() 和 release() 之间的代码放到 with lock 块中即可。