Python - 多线程、多进程、进程池(代码详解,完整注释)

Python - 多线程、多进程、进程池(代码详解,完整注释)

我们以爬虫只作为业务背景来对多任务进行演示。

爬虫一般分为两个角色:

一个角色用来访问 url,解析页面,这里将其命名为 viewer;
另一个用来获取、收集待访问 url,这里称其为 getter.

那么闲话少说,注释写得很清楚,直接看代码~

  • 多线程

    多线程,相当于在同一个进程中产生多个代码执行箭头,不同的箭头同时执行代码的不同位置。

    from collections import deque
    from random import randint
    import threading
    import time
    import os
    
    if __name__ == '__main__':
    	# 开启多线程的程序
    	multi_threading()
    
    def multi_threading():
    	# 使用 threading 包中的 lock 锁机制
        lock = threading.Lock()
    	# 使用 collections 中 deque 双端队列(多线程安全)
        urls = deque()
    	# 两个线程用来获取目标 url
        t1 = threading.Thread(target=getter, args=(lock, urls))
        t2 = threading.Thread(target=getter, args=(lock, urls))
        # 一个线程用来访问目标 url
        t3 = threading.Thread(target=viewer, args=(lock, urls))
    	# 开启三个线程
        t1.start()
        t2.start()
        t3.start()
    
    
    def getter(lock, urls):
    	# 获取 url 的方法
        while 1:
            time.sleep(0.5)
            # 生成 url
            url = "https://www."+"".join([str(randint(0, 10)) for _ in range(10)])+".com"
            # 上锁
            lock.acquire()
            # 添加 url
            urls.append(url)
            print(f"{os.getpid()} +++ get: {url}")
            # 解锁
            lock.release()
    
    
    def viewer(lock, urls):
        while 1:
            time.sleep(1)
            # 上锁
            lock.acquire()
            # 当 urls 中为空(即没有待访问 url)时,viewer 待命
            try:
                url = urls.pop()
                print(f"{os.getpid()} --- view:{url}", len(urls))
            except IndexError:
                print("viewer is waiting")
            # 解锁
            lock.release()
    

    代码的输出为:

    11700 +++ get: https://www.34161024993.com
    11700 +++ get: https://www.00306103387.com
    11700 --- view:https://www.00306103387.com 1
    11700 +++ get: https://www.12910292324.com
    11700 +++ get: https://www.2690777391.com
    11700 +++ get: https://www.91190651083.com
    11700 +++ get: https://www.791006241068.com
    11700 --- view:https://www.791006241068.com 4
    

    可以看到,在不同的线程中,进程的 pid 是相同的,都是 11700,说明他们都属于一个进程。

    如果不满意内置的 threading,我们还可以自己实现,只需要继承 threading.Thread,并重写 run 方法即可。

    class My_Thread(threading.Thread):
        def run(self):
            print("run")
    
  • 多进程

    多进程,相当于将代码文件复制多份,不同的进程在不同的程序文件中执行不同位置的代码。它将复制多份代码,所以较耗费内存。

    多进程与多线程非常相似,只需稍作改动,多线程代码就可转变为多进程。

    from random import randint
    import multiprocessing
    import time
    import os
    
    def multi_processing():
    	# 锁使用 multiprocessing 当中自有的
        lock = multiprocessing.Lock()
    	# 使用自有的队列 Queue,
    	# 如果使用 collections.deque 是无法完成进程间通信的
        urls = multiprocessing.Queue()
    	# 只需将 threading 改成 multiprocessing.Process 即可
        t1 = multiprocessing.Process(target=getter, args=(lock, urls))
        t2 = multiprocessing.Process(target=getter, args=(lock, urls))
        t3 = multiprocessing.Process(target=viewer, args=(lock, urls))
    
        t1.start()
        t2.start()
        t3.start()
    
    
    def getter(lock, urls):
        while 1:
            time.sleep(0.5)
            url = "https://www."+"".join([str(randint(0, 10)) for _ in range(10)])+".com"
            lock.acquire()
            urls.put(url)
            print(f"{os.getpid()} +++ get: {url}")
            lock.release()
    
    
    def viewer(lock, urls):
        while 1:
            time.sleep(0.2)
            lock.acquire()
            try:
            	# 这里需要注意,如果使用 urls.get(),当 urls 为空时,进程会停在这里
            	# 会等待 urls 不为空的时候继续执行,
            	# 又因为已经将锁锁住,所以会导致死锁状态。
            	# 所以如果使用 urls.get() 应进行 urls.empty() 判空操作。
            	# urls.get_nowait() 在 urls 为空时会抛出异常,而不会等待,不会造成死锁状态
                url = urls.get_nowait()
                print(f"{os.getpid()} --- view:{url}", urls.qsize())
            except Exception:
                print("viewer is waiting")
            lock.release()
    

    程序运行结果如下:

    viewer is waiting
    viewer is waiting
    5016 +++ get: https://www.5844833786.com
    15112 +++ get: https://www.8704855733.com
    2536 --- view:https://www.5844833786.com 1
    2536 --- view:https://www.8704855733.com 0
    

    可以看到有三个进程ID 5016,15112,2536 ,所以多进程确实有多个进程运行。

    • 进程池
      import multiprocessing
      import time
      import os
      
      if __name__ == '__main__':
      	processing_pool()
      
      def processing_pool():
      	# 在进程池中准备 2 个进程
          pool = multiprocessing.Pool(2)
      
      	# 共有 4 个任务要执行
      	# 2 个进程要去执行 4 个任务,进程数是不够的
      	# 进程池的机制为,当一个进程执行完任务后将重新回到进程池中备用
      	# 如果还有任务要执行,那么就从进程池中拿出空闲的进程使用
          for i in range(4):
              pool.apply_async(run, args=(i, ))
      	# 先关闭进程池,意思为进程池不再接受新的任务
          pool.close()
          # 将进程加入到主进程中,防止子进程尚未结束,主进程已经执行完,导致杀死子进程。
          # 如果没有 pool.join(),那么主进程在执行完 pool.close() 后其代码结束,所以主进程会关闭。
          # 而加入 pool.join() 意味着子进程的代码也算在主进程代码内,子进程没完,则主进程也没完
          # 此时主进程会等待子进程结束后再结束。
          pool.join()
      
      
      def run(i):
      	print(f"job_id:{i}")
          # 当 n==3 时,当前进程任务执行完毕
          for n in range(3):
              time.sleep(0.5)
              print(f"p_id:{i}  n:{n+1}")
         	print(f"job_id:{i}  ----- stop! ")
      

      执行结果为:

      job_id:0
      job_id:1
      p_id:0  n:1
      p_id:1  n:1
      p_id:0  n:2
      p_id:1  n:2
      p_id:0  n:3
      job_id:0  ----- stop! 
      job_id:2
      p_id:1  n:3
      job_id:1  ----- stop! 
      job_id:3
      p_id:2  n:1
      p_id:3  n:1
      p_id:2  n:2
      p_id:3  n:2
      p_id:2  n:3
      job_id:2  ----- stop! 
      p_id:3  n:3
      job_id:3  ----- stop! 
      
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值