python并发编程

并发编程是咱们编程中经常使用的优化程序性能的手段,能提升CPU的使用率。通常使用是多线程,多进程,协程python

 1、python的全局解释锁GIL

咱们目前跑的python程序大多数都是在cpython上运行的。cpython是有一个全局解释锁,具体什么意思,能够两个方面理解编程

  1. 在同一时刻,只能运行一个线程
  2. 没法将多个线程映射到多个cpu上运行

也就是说python多线程是有必定局限性的,对于io密集型的任务,咱们能够很好的利用多线程,对于计算密集型的任务,多线程就没有意义了,只能利用多进程提升性能安全

2、简单多线程编程

第一种形式:使用threading库,直接使用Thread类多线程

from threading import Thread
import time

def fun(n):
    # do something
    time.sleep(n)
    print(n)

t1 = Thread(target=fun, args=(1,))
t2 = Thread(target=fun, args=(2,))

# 启动线程
t1.start()
t2.start()

# 等待线程结束
t1.join()
t2.join()

 

第二种形式:使用threading库,继承Thread类并发

from threading import Thread
import time

class MyThread(Thread):
    def __init__(self, n):
        self.n = n
        super().__init__()

    def run(self):
        time.sleep(self.n)
        print(self.n)


t1 = MyThread(1)
t2 = MyThread(2)

t1.start()
t2.start()

t1.join()
t2.join()

 

3、线程安全

多个线程在同一个进程中运行时,是共享内存空间的,这就有可能引发一个线程在修改一个变量到一半的时候去休息了,回来的时候发现这个变量变了,这就会产生问题app

如何保证线程安全,通常有下面几个方法性能

  1. 使用私有变量,每一个线程都只知道本身的变量,其余线程没法修改,通常经过局部变量实现
  2. 经过拷贝数据到本身的空间,本身修改本身拷贝的数据,这样不会影响其余线程,通常经过thread.local实现
  3. 控制共享变量的访问方式,同一时刻只能有一个线程修改,通常经过加锁实现

还有一些其余方法保证线程安全,不一样的场景,保证线程安全的方法也不一样,须要合理采用。下面用代码说明一下优化

首先,咱们不加锁,有两个线程共同操做一个共享变量ui

# 没有使用锁的状况
from threading import Thread

n = 0

def add():
    global n
    for i in range(1000000):
        n += 1

def sub():
    global n
    for i in range(1000000):
        n -= 1


t1 = Thread(target=add)
t2 = Thread(target=sub)

t1.start()
t2.start()

t1.join()
t2.join()

print(n)

 

输出的结果,发现不是咱们想象中的0,这就是由于两个线程运行期间,一个线程把 对n的操做执行到一半时候,去休息了,回来继续执行剩下一半的过程当中,另外一个线程修改了n,这样就形成了线程不安全的问题,最后的结果不一致。spa

加锁后的代码

from threading import Thread, Lock

n = 0
lock = Lock()

def add(lock):
    global n
    for i in range(1000000):
        lock.acquire()
        n += 1
        lock.release()

def sub(lock):
    global n
    for i in range(1000000):
        lock.acquire()
        n -= 1
        lock.release()


t1 = Thread(target=add, args=(lock,))
t2 = Thread(target=sub, args=(lock,))

t1.start()
t2.start()

t1.join()
t2.join()

print(n)

 

此次输出的结果都是0了,咱们在整个修改共享变量的过程当中,加了锁,在锁未释放的时候,其余线程是运行不了的

4、线程间通讯

上面咱们知道存在线程安全的问题,因此通讯的过程当中,咱们要考虑线程安全的问题

线程间通讯最经常使用的是队列,python提供了一个线程安全的队列,from queue import Queue,在多个线程之间操做队列里的元素都是安全的

还有更加复杂的线程通讯机制,好比条件变量,信号量等,python也提供了相应的模块

5、线程池

有时候,咱们有不少条数据,咱们但愿同一时刻总共有5个线程来处理这一批数据,一个线程结束后,再启动另外一个线程,总数不能超过5个,这时候线程池就能够很好的解决咱们的问题了

from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures import wait
import time

pools = ThreadPoolExecutor(5)

data = list(range(20))
res = []

def fun(n):
    time.sleep(2)
    print(n)
    return n

for n in data:
    res.append(pools.submit(fun, n))

# 等待全部线程执行完毕 wait(res)

线程池还给咱们提供了更多的功能,咱们能够获取线程返回的结果

from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures import as_completed
import time

pools = ThreadPoolExecutor(5)

data = list(range(20))
res = []

def fun(n):
    time.sleep(2)
    return n

for n in data:
    res.append(pools.submit(fun, n))

# 获取线程结束后的结果
for r in as_completed(res):
    print(r.result())

 

6、多进程编程

关于python多进程,在大多数业务开发中,用的比较少,我只给出一个简单的例子(实际上进程在运行的时候比这个负责,是会拷贝一份父进程的信息的)

from multiprocessing import Process
import time

def fun(n):
    # do something
    time.sleep(n)
    print(n)
if __name__ == "__main__":
    
    p1 = Process(target=fun, args=(1,))
    p2 = Process(target=fun, args=(2,))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

 

7、总结

并发编程是咱们提升程序性能的经常使用手段,通常来讲就是多线程,多进程,协程,并发编程的时候,咱们还有许多问题须要考虑,线程安全,通讯等等。