之前在杭州一家互聯網公司任職網絡爬蟲,用的還是PHP,現在主流的爬蟲語言應當非Python莫屬了,趕緊學起來!
進程就是操作系統中執行的一個程序,操作系統以進程爲單位分配存儲空間,每個進程都有自己的地址空間、數據棧以及其他用於跟蹤進程執行的輔助數據,操作系統管理所有進程的執行,爲它們合理的分配資源。進程可以通過
fork
或spawn
的方式來創建新的進程來執行其他的任務,不過新的進程也有自己獨立的內存空間,因此必須通過進程間通信機制(IPC,Inter-Process Communication)
來實現數據共享,具體的方式包括管道、信號、套接字、共享內存區等。
一個進程還可以擁有多個併發的執行線索,簡單的說就是擁有多個可以獲得CPU調度的執行單元,這就是所謂的線程。由於線程在同一個進程下,它們可以共享相同的上下文,因此相對於進程而言,線程間的信息共享和通信更加容易。當然在單核CPU系統中,真正的併發是不可能的,因爲在某個時刻能夠獲得CPU的只有唯一的一個線程,多個線程共享了CPU的執行時間。使用多線程實現併發編程爲程序帶來的好處是不言而喻的,最主要的體現在提升程序的性能和改善用戶體驗。Python既支持多進程又支持多線程,因此使用Python實現併發編程主要有3種方式
:多進程、多線程、多進程+多線程
。
1. Python多進程(Multiprocessing)
Python的OS
模塊封裝了常見的系統調用,比如fork()函數
,進程可以利用它在Python中輕鬆的創建子進程。子進程是父進程的拷貝,但是子進程擁有自己的進程識別號PID(Process Identification)
,父進程中可以通過fork()函數
的返回值得到子進程的PID,而子進程中的返回值永遠都是0
。子進程可以通過getpid()函數
獲取父進程的PID。一個父進程可以fork出很多子進程。
由於Windows系統沒有fork()調用,因此要實現跨平臺的多進程編程,可以使用
multiprocessing
模塊的Process
類來創建子進程,而且該模塊還提供了更高級的封裝,例如批量啓動進程的進程池(Pool)
、用於進程間通信的隊列(Queue)
和管道(Pipe)
等。
from multiprocessing import Process
import os
def run_proc(name):
print('Run child process %s(%s)...'%(name,os.getpid()))
if __name__=='__main__':
print('Parent process %s'%os.getpid())
p = Process(target=run_proc,args=('test',))
print('Child process will start')
p.start()
p.join()
print('Child process end')`
如果要啓動大量的子進程,可以採用進程池(Pool)
的方式來創建子進程:
Pool常用的方法:
方法 | 含義 |
---|---|
apply() | 同步執行(串行) |
apply_async() | 異步執行(並行) |
terminate() | 立刻關閉進程池 |
join() | 主進程等待所有子進程執行完畢。必須在close或terminate()之後使用 |
close() | 等待所有進程結束後,才關閉進程池 |
from multiprocessing import Pool
import os,time,random
def long_time_task(name):
print('Run task %s (%s)...'%(name,os.getpid()))
start = time.time()
time.sleep(random.random*3)
end = time.time()
print('Task %s runs %0.2f seconds.'%(name,(end-start)))
if __name__ == '__main__':
print('Parent process %s.'%os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task,args=(i,))
print('Waiting for all subprocess done....')
p.close()
p.join()
print('All subprocess done')
在Unix/Linux下,可以使用
fork()
調用實現多進程。要實現跨平臺的多進程,可以使用
multiprocessing
模塊。進程間通信是通過
Queue、Pipes
等實現的。
參考資料:https://www.liaoxuefeng.com/wiki/1016959663602400/1017628290184064,感謝廖老師網站上通俗易懂的講解!
Python多線程
Python中使用線程有兩種方式:函數或者用類來包裝線程對象
函數式:調用 _thread 模塊中的start_new_thread()函數來產生新線程。
Python3 通過兩個標準庫
_thread
和threading
提供對線程的支持。
_thread 提供了低級別的、原始的線程以及一個簡單的鎖,它相比於 threading 模塊的功能還是比較有限的。 threading 模塊除了包含 _thread 模塊中的所有方法外,還提供的其他方法:
方法名 | 解釋 |
---|---|
threading.currentThread() |
返回當前的線程變量 |
threading.enumerate() |
返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程 |
threading.activeCount() |
返回正在運行的線程數量,與len(threading.enumerate())有相同的結果 |
類式: THread類提供以下方法來處理線程:
方法 | 解釋 |
---|---|
un() | 用以表示線程活動的方法 |
start() | 啓動線程活動 |
join([time]) | 等待至線程中止。這阻塞調用線程直至線程的join() 方法被調用中止-正常退出或者拋出未處理的異常-或者是可選的超時發生 |
isAlive() | 返回線程是否活動的 |
getName() | 返回線程名 |
setName() | 設置線程名 |
#-*-coding:gbk-*-
import threading
import time
exitFlag = 0
class myThread(threading.Thread):
def __init__(self,threadID,name,counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print('開始線程:'+self.name)
print_time(self.name,self.counter,5)
print("退出線程:"+self.name)
def print_time(threadName,delay,counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print("%s:%s"%(threadName,time.ctime(time.time())))
counter-=1
thread1 = myThread(1,"Thread-1",1)
thread2 = myThread(2,"Thread-2",2)
if __name__ == '__main__':
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print_time('退出主線程')
參考網站:https://www.runoob.com/python3/python3-multithreading.html
多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。在這種情況下,
“鎖”(Lock)
就可以派上用場了。我們可以通過“鎖”來保護“臨界資源”
,只有獲得“鎖”的線程才能訪問“臨界資源”,而其他沒有得到“鎖”的線程只能被阻塞起來,直到獲得“鎖”的線程釋放了“鎖”,其他線程纔有機會獲得“鎖”,進而訪問被保護的“臨界資源”。
from time import sleep
from threading import Thread, Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先獲取鎖才能執行後續的代碼
self._lock.acquire()
try:
new_balance = self._balance + money
sleep(0.01)
self._balance = new_balance
finally:
# 在finally中執行釋放鎖的操作保證正常異常鎖都能釋放
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
account = Account()
threads = []
for _ in range(100):
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
for t in threads:
t.join()
print('賬戶餘額爲: ¥%d元' % account.balance)
if __name__ == '__main__':
main()
獲得鎖的線程用完後一定要釋放鎖,否則那些苦苦等待鎖的線程將永遠等待下去,成爲死線程。所以我們用
try...finally
來確保鎖一定會被釋放。
新概念
協程:單線程的異步編程模型,有了協程的支持,就可以基於事件驅動編寫高效的多任務程序。