Python &進程與線程

之前在杭州一家互聯網公司任職網絡爬蟲,用的還是PHP,現在主流的爬蟲語言應當非Python莫屬了,趕緊學起來!

進程就是操作系統中執行的一個程序,操作系統以進程爲單位分配存儲空間,每個進程都有自己的地址空間、數據棧以及其他用於跟蹤進程執行的輔助數據,操作系統管理所有進程的執行,爲它們合理的分配資源。進程可以通過forkspawn的方式來創建新的進程來執行其他的任務,不過新的進程也有自己獨立的內存空間,因此必須通過進程間通信機制(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 通過兩個標準庫_threadthreading 提供對線程的支持。
_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來確保鎖一定會被釋放。

新概念
協程單線程的異步編程模型,有了協程的支持,就可以基於事件驅動編寫高效的多任務程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章