Python并发编程以及系统常用模块
全局解释器锁GIL
它是在实现Python解析器时所引入的一个概念
GIL是一把全局排他锁,同一时刻只有一个线程在运行。
毫无疑问全局锁的存在会对多线程的效率有不小影响。甚至就几乎等于Python是个单线程的程序。
multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是
它使用了多进程而不是多线程。每个进程有自己独立的GIL,因此也不会出现进程之间的GIL争抢。
顺序执行单线程与同时执行两个并发线程
from threading improt Thread
import time
def my_counter():
i = 0
for _ in range(1000000):
i = i + 1
return True
依次创建线程后 join 来阻塞进程直到线程执行完毕, 因此是顺序执行单线程
def main():
thread_array = {}
start_time = time.time()
for tid in range(2):
t = Thread(target = my_counter)
t.start()
t.join()
end_time = time.time()
print("Total time:{}".format(end_time - start_time))
if __name__ == '__main__':
main()
先创建完所有线程start后,最后再依次轮询所有线程join() 因此是并发执行
def main():
thread_array = {}
start_time = time.time()
for tid in range(2):
t = Thread(target = my_counter)
t.start()
thread_array[tid] = t
for i in range(2):
thread_array[i].join()
end_time = time.time()
print("Total time:{}".format(end_time - start_time))
multiprocessing模块
是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,下面是示例:
from multiprocessing import Process
import time
def f(n):
time.sleep(l)
print(n*n)
if __name__ == '__main__':
for i in range(10):
p = Process(target = f, args = [i,])
p.start()
这个程序如果用单进程写则需要执行10s以上的时间,而用多进程则启动10个进程并行执行,只需要1s多的时间
进程间通信Queue
from multiprocessing import Process,Queue
import time
def write(q):
for i in ['A','B','C','D','E']:
print('Put %s to queue' % i)
q.put(i)
time.sleep(0.5)
def read(q):
while True:
v = q.get(True)
print('get %s from queue' % v)
if __name__ == '__main__':
q = Queue()
pw = Process(target = write, args=(q,))
pr = Process(target = read, args=(q,))
pw.start()
pr.start()
pr.join()
pr.terminate()
进程池Pool
用于批量创建子进程,可以灵活控制子进程的数量,之前我们创建进程使用Process创建一个,这个Pool批量创建多个,更简单了
from multiprocessing import Pool
import time
def f(x):
print(x*x)
time.sleep(2)
return x*x
if __name__ == '__main__':
'''定义启动的进程数量'''
pool = Pool(processes=5)
res_list = []
for i in range(10):
'''以一部并行的方式启动进程,如果要同步等待的方式,可以在每次启动进程之后调用res.get()方法,也可以使用Pool.apply '''
res = pool.apply_async(f, [i,])
print('------------:',i)
res_list.append(res)
pool.close()
pool.join()
for r in res_list:
print("result", r.get(timeout=5))
进程与线程对比
在一般情况下,多线程是可以共享同一个进程的内存资源,无需额外操作即可直接交换多个线程间数据,这是使用多线程的方便之处,当然也正由于数据共享,引发了数据同步问题。
但进程之间是数据隔离的,内存资源相互独立,不能直接共享。
由于进程之间不共享数据,对于每个进程res_list[]都是空列表,所以在并发执行的时候,执行结果是
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
------------------------threading---------------------------
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
输出结果各自独立的res_list,也印证了多进程数据独立不共享。
from multiprocessing import Process
import threading
import time
lock = threading.Lock()
def run(info_list, n):
lock.acquire() # lock.acquire()会锁住临界区,教程中说是会Lock上进程或线程使用到的所有资源,包括内存数据。我觉得不对,应该是只Lock住 lock.acquire() {block} lock.release()包括的代码块
info_list.append(n)
lock.release()
print('%s\n' % info_list)
if __name__ == '__main__':
info = []
for i in range(10):
# target为子进程执行的函数,args为需要给函数传递的参数
p = Process(target=run, args=[info, i]) # args对应run的参数列表
p.start()
p.join()
time.sleep(1) # 这里是为了输出整齐让主进程的执行等一下子进程
print('---------------threading----------------')
for i in range(10):
p = threading.Thread(target=run, args=[info, i])
p.start()
p.join()