zoukankan      html  css  js  c++  java
  • python并发编程

    并发编程是我们编程中常用的优化程序性能的手段,能提高CPU的使用率。一般使用是多线程,多进程,协程

     一、python的全局解释锁GIL

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

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

    也就是说python多线程是有一定局限性的,对于io密集型的任务,我们可以很好的利用多线程,对于计算密集型的任务,多线程就没有意义了,只能利用多进程提高性能

    二、简单多线程编程

    第一种形式:使用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()

    三、线程安全

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

    如何保证线程安全,一般有下面几个方法

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

    还有一些其他方法保证线程安全,不同的场景,保证线程安全的方法也不同,需要合理采用。下面用代码说明一下

    首先,我们不加锁,有两个线程共同操作一个共享变量

    # 没有使用锁的情况
    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,这样就造成了线程不安全的问题,最后的结果不一致。

    加锁后的代码

    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了,我们在整个修改共享变量的过程中,加了锁,在锁未释放的时候,其他线程是运行不了的

    四、线程间通信

    上面我们知道存在线程安全的问题,所以通信的过程中,我们要考虑线程安全的问题

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

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

    五、线程池

    有时候,我们有很多条数据,我们希望同一时刻总共有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())

    六、多进程编程

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

    七、总结

    并发编程是我们提高程序性能的常用手段,一般来说就是多线程,多进程,协程,并发编程的时候,我们还有许多问题需要考虑,线程安全,通信等等。

  • 相关阅读:
    CSS选择器的权重与优先规则
    excel上传--phpExcel读取xls、xlsx
    反射与代理设计模式
    Map集合
    接口实际应用-工厂代理模式
    代码模型:对象比较
    Stream数据流
    集合输出接口-Iterator迭代输出-古老枚举输出:Enumeration
    Set集合接口-HashSet_TreeSet理解
    List类集接口-ArrayList
  • 原文地址:https://www.cnblogs.com/time-read/p/11119032.html
Copyright © 2011-2022 走看看