python之併發編程:多進程與多線程

一:多進程

       進程(process),是計算機中已運行程序的實體,是線程的容器;一個進程至少有一個線程

1,父進程與其子進程:
       各進程的內存空間是互相隔離的
       進程創建時,爲該進程生成一個PCB(進程控制塊);進程終止時,回收PCB。每個進程都有一個非負的唯一進程ID(PID)。雖然是唯一的,但是PID可以重用,當一個進程終止後,其他進程就可以使用它的PID了。

       linux:調用fork創建子進程
       PID爲0的進程爲調度進程,該進程是內核的一部分,也稱爲系統進程;PID爲1的進程爲init進程,它是一個普通的用戶進程,但是以超級用戶特權運行;PID爲2的進程是頁守護進程,負責支持虛擬存儲系統的分頁操作。  
       子進程剛開始,內核並沒有爲它分配物理內存,而是以只讀的方式共享父進程內存,只有當子進程寫時,才分配物理內存,複製父進程的數據給子進程,即“copy-on-write”

       windows:調用CreateProcess創建子進程
       子進程創建時就爲它分配物理內存,複製父進程的數據給子進程

       殭屍進程:子進程結束了,父進程還未結束,此時,子進程的一些資源(如PID)還未回收,需要等到父進程結束才能回收,就稱這樣的子進程爲殭屍進程

       孤兒進程(linux):父進程結束了,子進程還在,此時,這些子進程託管給init進程來管理

2,創建子進程的兩種方式:

from multiprocessing import Process
import os
# multiprocessing 是一個包,threading是一個模塊,它倆有着相似的API

#方式一:自定義創建子進程的類,繼承於Process類
class Myprocess(Process):
    def __init__(self,runner):
        super().__init__()
        self.runner = runner
        self.name = '子進程'         # 自定義子進程的名字,默認爲Myprocess-1,Myprocess-2
    def run(self):     # 子進程的任務函數,自動執行,名字必須爲run
        print('%s is running' % self.runner)
        print(self.name)
        print('PID爲:%s,其父進程PID爲:%s' %(os.getpid(),os.getppid()))

if __name__ == '__main__':
    subprocess_obj1 = Myprocess('rock')
    subprocess_obj2 = Myprocess('mike')
    subprocess_obj1.start()
    subprocess_obj2.start()

# 方式二:使用Process類
def task(name):
    print('%s is running' %name)
    print('PID爲:%s,其父進程PID爲:%s' %(os.getpid(),os.getppid()))

if __name__ == '__main__':
    # 必須寫在 if __name__ == '__main__': 裏面
    subprocess_obj = Process(target=task,args=('rock',),kwargs={},name='自定義進程名字')
    subprocess_obj.start()    # 向操作系統申請開啓該子進程

    subprocess_obj.daemon = True  # 把該子進程對象設置爲守護進程
    # 守護進程--被守護的進程代碼一執行完守護進程就結束

    print(subprocess_obj.name)   # 取得該子進程對象的名字
    print(subprocess_obj.pid)    # 取得該子進程對象的PID
    subprocess_obj.join()        # 進入阻塞狀態,等待該子進程執行結束
    subprocess_obj.is_alive()    # 判斷該子進程對象是否仍存活
    subprocess_obj.terminate()   # 向操作系統申請結束該子進程
    print('嘿,我是父進程,我的PID爲%s' %os.getpid())

3,Queue 與 JoinableQueue

       IPC機制:inter-process communication 進程間通信機制
       主要有管道,隊列等。。。
       Queue 與 JoinableQueue:

       共享的內存空間;有互斥鎖機制,以保障共享數據的安全

from multiprocessing import Queue,JoinableQueue
# Queue
# Returns a process shared queue implemented using a pipe and a few locks/semaphores
q = Queue(5)   # 隊列對象,參數5表示隊列中能存的值的個數
q.put('abc')   # 往隊列裏面存值;當隊列滿了時,這行語句執行被阻塞
q.get()        # 從隊列裏面取值;當隊列爲空時,這行語句執行被阻塞

# JoinableQueue
# JoinableQueue, a Queue subclass, is a queue which additionally has task_done() and join() methods.
q1 = JoinableQueue()
q1.put('abc')
q1.get()
# task_done()用於與join()搭配使用:
q1.task_done()   # 隊列每被取走一個值就通知一次隊列,通過這種方式能知道隊列的值是否被取完
q1.join()        # 處於阻塞狀態,等待隊列所有值被取走


二:多線程

       線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。

1,進程與其線程:

       一個進程下的所有線程共享該進程的全部資源

2,創建子線程的兩種方式:

from threading import Thread,current_thread
#方式一:自定義創建子線程的類,繼承於Thread類
class Mythread(Thread):
    def __init__(self,runner):
        super().__init__()
        self.runner = runner
        # self.name = '子線程'         # 自定義子線程的名字,默認爲Thread-1,Thread-2
    def run(self):    # 子線程的任務函數,自動執行,名字必須爲run
        print('%s is running' % self.runner)
        print('my name is %s' %current_thread().name)

if __name__ == '__main__':
    subthread_obj1 = Mythread('rock')
    subthread_obj2 = Mythread('mike')
    subthread_obj1.start()
    subthread_obj2.start()

# 方式二:使用Process類
def task(name):
    print('%s is running' %name)
    print('my name is %s' %current_thread().name)   # Thread-1

if __name__ == '__main__':
    subthread_obj = Thread(target=task,args=('rock',))
    subthread_obj.start()    # 向操作系統申請開啓該子線程

    print(subthread_obj.getName())
    print(subthread_obj.name)     # 取得該子線程對象的名字
    subthread_obj.daemon = True   # 設置該子線程對象爲守護線程
    # 守護線程--被守護的線程結束(代碼執行完不一定馬上就結束)守護線程才結束

    subthread_obj.join()        # 進入阻塞狀態,等待該子線程執行結束
    subthread_obj.is_alive()    # 判斷該子線程對象是否仍存活
    print('嘿,我是主線程,我的名字是%s' %current_thread().name)   # MainThread

3,線程隊列 queue

import queue
# The queue module implements multi-producer, multi-consumer queues. It is especially useful
# in threaded programming when information must be exchanged safely between multiple threads.

q = queue.Queue()  # 普通隊列  先進先出
q.put(1)
q.get()

# task_done()用於與join()搭配使用:
q.task_done()   # 隊列每被取走一個值就通知一次隊列,通過這種方式能知道隊列的值是否被取完
q.join()  # 處於阻塞狀態,等待隊列所有值被取走

q1 = queue.LifoQueue()  # 棧隊列  先進後出
q1.put('a')
q1.get()
q1.task_done()
q1.join()

q2 = queue.PriorityQueue()  # 優先級隊列,優先級最高的先出來
q2.put((1,'abc'))    # 參數格式:(priority_number, data)  數字越小優先級越高
q2.get()
q2.task_done()
q2.join()


三:三種鎖的介紹

# 1,互斥鎖
from multiprocessing import Lock
# from threading import Lock

lock = Lock()  # 創建互斥鎖對象,該對象有兩種狀態:locked 和 unlocked ;初始狀態爲 unlocked

def task(lock):
    lock.acquire()   # acquire(block=True, timeout=None)
    # 實現原理:
    # 當鎖對象是unlocked狀態時,該函數調用正常,並立即將鎖對象的狀態轉爲locked狀態;
    # 當鎖對象是locked狀態時,該函數調用被阻塞
    pass
    lock.release() # 把鎖對象的狀態由locked 轉爲 unlocked。若鎖對象已經是unlocked狀態時,會報錯

# 2,遞歸鎖
# from multiprocessing import RLock
from threading import RLock

r_lock = RLock()  # 這個類實現了可重入的鎖對象,其初始遞歸級別爲0

r_lock.acquire()
# 某線程第一次調用該函數時:
# 當鎖對象的遞歸級別爲0時,該函數調用正常,且立即將遞歸級別加1;其後該線程可多次調用該函數,每次將遞歸級別加1
# 當鎖對象的遞歸級別不爲0時,阻塞

r_lock.release()
# 將鎖對象的遞歸級別減1

# 3,信號量
# from multiprocessing import Semaphore
from threading import Semaphore

s_lock = Semaphore(5)  # 產生信號量對象,內部計數器初始值爲5;不傳值則默認值爲1

s_lock.acquire()
# Acquire a semaphore
# 當內部計數器值大於0時,該函數調用正常,且內部計數器立即執行減1操作;
# 當內部計數器值爲0時,該函數調用被阻塞,直到內部計數器大於0時,恢復調用正常,內部計數器減1

s_lock.release()
# Release a semaphore
# 內部計數器立即執行加1操作
# 各種鎖可以使用with聲明語句,語法如下:
with some_lock:
    # do something...

# 其等同於:
some_lock.acquire()
try:
    # do something...
finally:
    some_lock.release()


四:進程池與線程池

       進程池:裝子進程的池子,能控制子進程的個數,是一種保護機制,確保進程個數在機器性能可承受範圍內

       線程池:裝子線程的池子,能控制子線程的個數,是一種保護機制,確保線程個數在機器性能可承受範圍內

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

# ProcessPoolExecutor
# An Executor subclass that executes calls asynchronously using a pool of at most max_workers processes.

# ThreadPoolExecutor
# An Executor subclass that uses a pool of at most max_workers threads to execute calls asynchronously
def task(n):
    n +=1
    return n

def fn(future):
    res = future.result()
    pass

executor = ThreadPoolExecutor(5) # 可創建的子線程數量最大爲5,不傳參數則默認爲機器的cpu數*5
future = executor.submit(task,3)
# submit(fn, *args, **kwargs)  給線程池中的線程提交任務fn,即執行fn(*args, **kwargs);
# 返回一個表示可被調用的執行的Future對象
future.result()  # 取得任務執行完畢的返回值

future.add_done_callback(fn)  # 回調函數
# 把可調函數fn綁定給future對象;當future被取消或執行完畢時,fn會被調用(future作爲fn的唯一參數)

executor.shutdown()
# 等當前所有掛起的future對象執行完再回收所有的資源

# You can avoid having to call this method explicitly if you use the with statement,
# which will shutdown the Executor
with ThreadPoolExecutor(5) as e:
    e.submit(task,3)
    e.submit(task,4)

# 進程池與線程池的使用方式基本相同;
# 不同點:進程池,父進程執行回調函數綁定的函數fn(因爲future是父進程的變量);
# 線程池,某個線程繼續執行函數fn
if __name__ == '__main__':
    p = ProcessPoolExecutor(5) # 可創建的子進程數量最大爲5,不傳參數則默認爲機器的cpu數

    future = p.submit(task,4)
    print(future.result())  #Return the value returned by the call.

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