• python 并发编程多线程之进程池/线程池


    一、验证GIL锁的存在

    Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。

    from threading import Thread
    from multiprocessing import Process
    
    def task():
        while True:
            pass
    
    if __name__ == '__main__':
        for i in range(4):
            # t=Thread(target=task)  # 因为有GIL锁,同一时刻,只有一条线程执行,所以cpu不会满
            t=Process(target=task)   # 由于是多进程,进程中的线程会被cpu调度执行,4个cpu全在工作,就会跑满
            t.start()

    二、GIL锁与普通互斥锁的区别

    from threading import Thread, Lock
    import time
    
    mutex = Lock()
    money = 88
    
    
    def task():
        global money
        mutex.acquire()
        temp = money
        time.sleep(1)
        money = temp - 1
        mutex.release()
    
    
    if __name__ == '__main__':
        ll=[]
        for i in range(10):
            t = Thread(target=task)
            t.start()
            # t.join()  # 会怎么样?变成了串行,不能这么做
            ll.append(t)
        for t in ll:
            t.join()
        print(money)
    小结:GIL锁是不能保证数据的安全,普通互斥锁来保证数据安全

    三、io密集型和计算密集型处理

    -----以下只针对于cpython解释器
    -在单核情况下:
    -开多线程还是开多进程?不管干什么都是开线程
    -在多核情况下:
    -如果是计算密集型,需要开进程,能被多个cpu调度执行
    -如果是io密集型,需要开线程,cpu遇到io会切换到其他线程执行

    # 计算密集型
    # def task():
    #     count = 0
    #     for i in range(100000000):
    #         count += i
    #
    #
    # if __name__ == '__main__':
    #     ctime = time.time()
    #     ll = []
    #     for i in range(10):
    #         t = Thread(target=task)  # 开线程:42.68658709526062
    #         # t = Process(target=task)   # 开进程:9.04949426651001
    #         t.start()
    #         ll.append(t)
    #
    #     for t in ll:
    #         t.join()
    #     print(time.time()-ctime)
    
    
    ## io密集型
    def task():
        time.sleep(2)
    
    
    if __name__ == '__main__':
        ctime = time.time()
        ll = []
        for i in range(400):
            t = Thread(target=task)  # 开线程:2.0559656620025635
            # t = Process(target=task)   # 开进程:9.506720781326294
            t.start()
            ll.append(t)
    
        for t in ll:
            t.join()
        print(time.time()-ctime)

    五、死锁现象

    # 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

    import time
    from threading import Thread,Lock
    noodle_lock = Lock()
    fork_lock = Lock()
    def eat1(name):
        noodle_lock.acquire()
        print('%s 抢到了面条'%name)
        fork_lock.acquire()
        print('%s 抢到了叉子'%name)
        print('%s 吃面'%name)
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print('%s 抢到了叉子' % name)
        time.sleep(1)
        noodle_lock.acquire()
        print('%s 抢到了面条' % name)
        print('%s 吃面' % name)
        noodle_lock.release()
        fork_lock.release()
    
    for name in ['哪吒','nick','tank']:
        t1 = Thread(target=eat1,args=(name,))
        t2 = Thread(target=eat2,args=(name,))
        t1.start()
        t2.start()

    解决方法:递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

    import time
    from threading import Thread,RLock
    fork_lock = noodle_lock = RLock()
    def eat1(name):
        noodle_lock.acquire()
        print('%s 抢到了面条'%name)
        fork_lock.acquire()
        print('%s 抢到了叉子'%name)
        print('%s 吃面'%name)
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print('%s 抢到了叉子' % name)
        time.sleep(1)
        noodle_lock.acquire()
        print('%s 抢到了面条' % name)
        print('%s 吃面' % name)
        noodle_lock.release()
        fork_lock.release()
    
    for name in ['哪吒','nick','tank']:
        t1 = Thread(target=eat1,args=(name,))
        t2 = Thread(target=eat2,args=(name,))
        t1.start()
        t2.start()

     六、Semaphore信号量

    # Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据

    from  threading import Thread,Semaphore
    import time
    import random
    sm=Semaphore(5) # 数字表示可以同时有多少个线程操作
    
    def task(name):
        sm.acquire()
        print('%s 正在王者荣耀五排'%name)
        time.sleep(random.randint(1,5))
        sm.release()

    七、Event事件

    # 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
    # 比如一个线程等待另一个线程执行结束再继续执行

    from threading import Thread, Event
    import time
    import os
    
    event = Event()
    # 获取文件总大小
    size = os.path.getsize('a.txt')
    
    
    def read_first():
        with open('a.txt', 'r', encoding='utf-8') as f:
            n = size // 2  # 取文件一半,整除
            data = f.read(n)
            print(data)
            print('我一半读完了,发了个信号')
            event.set()
    
    
    def read_last():
        event.wait()  # 等着发信号
        with open('a.txt', 'r', encoding='utf-8') as f:
            n = size // 2  # 取文件一半,整除
            # 光标从文件开头开始,移动了n个字节,移动到文件一半
            f.seek(n, 0)
            data = f.read()
            print(data)
    
    
    if __name__ == '__main__':
        t1=Thread(target=read_first)
        t1.start()
        t2=Thread(target=read_last)
        t2.start()

    八、线程Queue

    进程queue和线程queue不是一个
     from multiprocessing import Queue
     线程queue
    from queue import Queue,LifoQueue,PriorityQueue
    线程间通信,因为共享变量会出现数据不安全问题,用线程queue通信,不需要加锁,内部自带
    queue是线程安全的
    三种线程Queue
        -Queue:队列,先进先出
        -PriorityQueue:优先级队列,谁小谁先出
        -LifoQueue:栈,后进先出
    '''
    # 如何使用
    # q=Queue(5)
    # q.put("老林")
    # q.put("老刘")
    # q.put("铁蛋")
    # q.put("钢弹")
    # q.put("金蛋")
    #
    #
    # # q.put("银蛋")
    # # q.put_nowait("银蛋")
    # # 取值
    # print(q.get())
    # print(q.get())
    # print(q.get())
    # print(q.get())
    # print(q.get())
    # # 卡住
    # # print(q.get())
    # # q.get_nowait()
    # # 是否满,是否空
    # print(q.full())
    # print(q.empty())
    
    # LifoQueue
    
    # q=LifoQueue(5)
    # q.put("lqz")
    # q.put("egon")
    # q.put("铁蛋")
    # q.put("钢弹")
    # q.put("金蛋")
    # #
    # # q.put("ddd蛋")
    # print(q.get())
    
    
    #PriorityQueue:数字越小,级别越高
    
    # q=PriorityQueue(3)
    # q.put((-10,'金蛋'))
    # q.put((100,'银蛋'))
    # q.put((101,'铁蛋'))
    # # q.put((1010,'铁dd蛋'))  # 不能再放了
    #
    # print(q.get())
    # print(q.get())
    # print(q.get())

    九、进程池

    在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。多进程是实现并发的手段之一,需要注意的问题是:

    1. 很明显需要并发执行的任务通常要远大于核数
    2. 一个操作系统不可能无限开启进程,通常有几个核就开几个进程
    3. 进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)

    例如当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

    我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数... 
    ps:对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。(线程池与进程池非常类似就是导的类不同)

    ```python
    # 1 如何使用
    from concurrent.futures import ProcessPoolExecutor
    pool = ProcessPoolExecutor(2)
    pool.submit(get_pages, url).add_done_callback(call_back)

    ```

    最终版

     初始版

     爬虫实例版

    import requests
    import re
    import time
    headers = {
    'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'
    }
    resbonse = requests.get(url='https://www.mzitu.com/',headers=headers)
    # print(resbonse.text)
    res = resbonse.text
    urls = re.findall('''data-original='(.*?)' ''',res,re.S)
    print(urls)
    for url1 in urls:
        time.sleep(0.1)
        file_name = url1.split('/')[-1]
        res = requests.get(url = url1)
        with open(file_name,'wb')as f:
            f.write(res.content)
     
    每天逼着自己写点东西,终有一天会为自己的变化感动的。这是一个潜移默化的过程,每天坚持编编故事,自己不知不觉就会拥有故事人物的特质的。 Explicit is better than implicit.(清楚优于含糊)
  • 相关阅读:
    JAVA_Collection容器
    ArrayList实现分组功能
    scrapy 安装出错 [err2] no such file or directory: 'README.rst'【已解决】
    python spyder 今天突然打不开了【已解决】
    SVN使用教程总结
    MVC框架浅析(基于PHP)
    Web性能优化方案
    野生程序员的故事
    js控制页面跳转,清缓存,强制刷新页面
    js中json处理总结之JSON.parse
  • 原文地址:https://www.cnblogs.com/kylin5201314/p/13565453.html
Copyright © 2020-2023  润新知