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()