我们以爬虫只作为业务背景来对多任务进行演示。
爬虫一般分为两个角色:
一个角色用来访问 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!
-