(十五) 學習筆記: Python程式(Process)相關

weixin_34391445發表於2018-06-03

一、程式相關概念

狹義定義:程式是正在執行的程式的例項(an instance of a computer program that is being executed)。

廣義定義:程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程式是作業系統分配資源的基本單位。它是作業系統動態執行的基本單元,在傳統的作業系統中,程式是基本的分配單元。

程式和程式的區別:
程式是程式在計算機上的一次執行活動。當你執行一個程式, 你就啟動了一個程式。
程式只是一組指令的有序集合, 它本身沒有任何執行的含義, 只是一個靜態實體。
而程式, 它是程式在某個資料集上的執行, 是一個動態實體。它因建立而產生, 因排程而執行, 因等待資源或事件而被處於等待狀態, 因完成任務而被撤消, 反映了一個程式在一定的資料集上執行的全部動態過程。

多工:
多工是指使用者可以在同一時間內執行多個應用程式, 每個應用程式被稱作一個任務, 一個任務就是一個程式(Process).Linux、windows就是支援多工的作業系統, 比起單任務系統它的功能增強了許多。比如:你一邊聊QQ,一邊聊微信,還聽著音樂,這就是多工,有幾個任務同時執行。

單核CPU實現多工的原理:
作業系統輪流讓多個任務交替執行 比如QQ執行了2us ,在切換到微信 執行了2us,在切換到其他,表面看起來像是在同時執行,是因為cpu的排程太快。

多核cpu實現多工的原理:
真正的並行執行多工只能在多核CPU上實現,但是由於當下任務數量遠遠多於CPU的核心數量,所以作業系統也會自動把很多工輪流排程到每個核心上執行。

併發和並行的區別:
併發和並行都可以處理“多工”, 二者的主要區別在於是否是“同時進行”多個的任務。
併發是指一個處理器同時處理多個任務, 在同一時刻只能有一條指令執行,但多個程式指令被快速的輪換執行,使得在巨集觀上具有多個程式同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若干段,使多個程式快速交替的執行。
並行是多個處理器同時處理多個不同的任務。
併發是邏輯上的同時發生, 而並行是物理上的同時發生。
並行在多處理器系統中存在, 而併發可以在單處理器和多處理器系統中都存在。

二、 Python中的多程式

multiprocessing模組

multiprocessing模組用來開啟子程式,並在子程式中執行我們定製的任務(比如一個函式)。multiprocessing模組的功能眾多:支援子程式、通訊和共享資料、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等元件。Python是通過建立Process物件來生成程式,然後呼叫它的start()方法啟動。
Process類屬性和方法

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

引數:
  group 值為None, 程式組
  target 子程式執行的目標函式,會在run()方法中呼叫
  name 程式名
  daemon 是否為守護程式(True/False),預設為None繼承父程式
方法:
  start() 啟動程式,同時呼叫子程式中的run()方法 
  run()  子程式啟動時呼叫的方法,在這個方法中執行target目標函式,可以在子類中重寫此方法
  terminate()  強制終止程式。
  is_alive()  返回程式是否處於活動狀態。返回boolean
  join([timeout]) 程式等待。如果可選引數timeout是None(預設值),則該方法將阻塞,直到被呼叫join()方法的子程式終止。如果timeout是一個正數,它最多會阻塞timeout秒。

簡單的建立子程式例項:

from multiprocessing import Process
def foo(a):
    print(a)

if __name__ == "__main__":
    p1 = Process(target=foo,args=('test',))  # args必須傳入元組
    p1.start()
    p1.join()  # 等待子程式執行完畢在結束主程式

注意:
(1)在windows中Process()必須放到 if __name__ == '__main__':下,目的是防止模組被匯入 造成多個程式執行的混亂。
(2)新增程式名.join(),是因為預設是主程式建立子程式之後繼續往下執行 執行完畢主程式結束, 通過呼叫這個方法主程式等待子程式執行結束之後,主程式在繼續向下直到結束。

可以通過兩種方式建立子程式:

(1) 通過編寫函式
from multiprocessing import Process
import os 

def foo(name):
      print("程式名為:", name, "程式id為", os.getpid())

if __name__ == '__main__':
    p1 = Process(target=foo,args=('zhangsan',))  # args傳入的是元素必須加,號
    p2 = Process(target=foo,args=('lisi',))
    p3 = Process(target=foo,args=('wangwu',))

    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print('主執行緒結束了!')


(2) 通過繼承Process類
from multiprocessing import Process
import os 

class MyClass(Process):
    def __init__(self, name):
        Process.__init__(self)  # 必須初始化父類
        self.name= name
    def run(self):
        print(self.name)  # 呼叫類的屬性
        print("執行了這裡")

if __name__ == '__main__':
    p1 = MyClass('zhangsan')
    p2 = MyClass('lisi')
    p3 = MyClass('wangwu')

    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print('主執行緒結束了!')

程式池
當使用multiprocessing中的Process動態建立多個程式,如果是幾個或十幾個程式還好,但是如果是上百個程式,手動去管理程式數量非常困難,此時可以使用程式池來管理程式。

from multiprocessing import Pool
import time

def foo():
      print(name)
      time.sleep(2)
if __name__ == '__main__':
    pool = Pool(processes=5)  # 程式併發數,建立5個程式,如果不新增引數,預設併發數為核心數
    
    for i in range(5):
        name = "name- %d" % i
        pool.apply_async(foo, args=(name,))  # 非同步非阻塞
        # pool.apply(foo, args=(name,))  # 阻塞
        print("for 迴圈中執行~~~~~")
    pool.close()  # 關閉程式池,不在向程式池加入子程式
    pool.join()  # 主程式等待子程式 執行完畢在繼續向下執行
    print("主程式執行到這裡了")  

注意:
(1)pool.apply(foo, args=(name,)) 阻塞式執行子程式,其每次只能向程式池新增一個程式,然後for迴圈會被堵塞等待,執行完一個程式,下一個程式才會被執行。
(2)pool.apply_async(foo, args=(name,)) 非同步非阻塞執行子程式,是不用等待當前程式執行完畢,可以根據系統排程來進行程式切換。
(3)呼叫join之前,先呼叫close函式,否則會出錯。執行完close後不會有新的程式加入到pool, join函式等待所有子程式結束

程式池pool.map()方法
如果有大量的資料新增到程式池裡 如果使用map會非常的方便,與內建的map函式用法行為基本一致,它會使程式阻塞直到返回結果。
第一個引數傳入方法 第二個方法傳入迭代器

def foo(x):
    time.sleep(2)
    print(x)


pool = Pool(2)
myList = ['zhangsan', 'lisi']
pool.map(foo, myList)  # 併發執行

pool.close()
pool.join()
  
print("主程式執行結束了")

獲取子程式的返回值

from multiprocessing import Pool
import time 
import os

def demo(x):
    # print(x)
    time.sleep(2)
    return x  # 將傳入進來的引數進行返回

if __name__ == '__main__':
    print(os.cpu_count())  # 返回cpu的核心數
    pool = Pool()
    for i in range(10):
        res = pool.apply_async(demo, args=(i,))
        print(res)  # 返回結果物件
        print(res.get())  # 獲取返回值
    pool.close()  # 不在向程式池放程式
    pool.join()  # 程式等待

三、程式間通訊

多程式全域性變數是否共享呢?

from multiprocessing import Process

name = 'zhangsan'

def demo():
  global name
  name = 'lisi'
  print("name==", name, "id(name)==", id(name))  #  ('name==', 'lisi', 'id(name)==', 139807614774032)

if __name__ == '__main__':
  p = Process(target=demo)
  p.start()
  p.join()
  print("name==", name, "id(name)==", id(name))  #  ('name==', 'zhangsan', 'id(name)==', 139807614773936)

  print("主程式執行結束了")

顯然,程式間全域性變數不共享,子程式可以獲取到主程式的全域性變數,但是不能修改,如果修改了全域性變數,只是修改了拷貝一份的同名的變數,記憶體地址是不同的。即程式間記憶體空間不是共享的。

程式間通訊

程式彼此之間互相隔離,要實現程式間通訊(IPC),multiprocessing模組提供兩種方法:管道和佇列。

管道Pipe

class multiprocessing.Pipe(duplex=True)
返回包含兩個Connection物件的元組,預設管道是全雙工的,左右兩個都可以傳送接受。如果建立管道的時候duplex設定為False,左邊只能用於接收,右邊只能用於傳送。

from multiprocessing import Process
from multiprocessing import Pipe

def demo(p1):
    print(p1.recv())  # a
    p1.send([1,2,3,4])
    print(p1.recv())  # ['a','b'] 

if __name__ == '__main__':
    p1, p2 = Pipe()
    print(Pipe())  #  (<read-write Connection, handle 8>, <read-write Connection, handle 9>)
    print(Pipe(False))  #  (<read-only Connection, handle 3>, <write-only Connection, handle 4>)

    p = Process(target=demo,args=(p1,))
    p.start()
    p2.send('a')
    p2.send(['a','b'])  # 傳送了兩次 需要接受兩次
    p.join()
    print(p2.recv())  # [1,2,3,4]

注意:
(1)p1.recv() 接收p2.send(obj)傳送的物件。如果沒有訊息可接收,recv方法會一直阻塞。如果連線的另外一端已經關閉,那麼recv方法會丟擲EOFError。
(2)p1.send(obj) 傳送一個使用pickle模組序列化obj物件。

佇列Queue

class multiprocessing.Queue([maxsize])
maxsize是佇列中允許的最大項數。如果省略此引數,則無大小限制。
方法:

qsize()    返回佇列的近似大小,這個數字是不可靠的。
empty() 如果為空返回True。
full()   如果佇列已滿返回True。
put(obj [, block[, timeout]])  將obj放入佇列中。如果可選引數block是True(預設)並且timeout是None(預設),則會一直阻塞,直到有空餘空間可用。如果timeout是一個正數,該方法會阻塞timeout指定的時間,直到該佇列有剩餘的空間,如果超過指定時間,會丟擲Queue.Full異常。如果block為False,但該Queue已滿,會立即丟擲Queue.Full異常。
get([block[, timeout]])   從佇列中取出一個元素。如果block為True(預設),並且timeout為正值,則在等待時間內沒有取到任何元素,會丟擲Queue.Empty異常。如果block為False,同時Queue有一個值可用,則立即返回該值,否則,如果佇列為空,則立即丟擲Queue.Empty異常。
get_nowait()   同q.get(False)。
put_nowait()   同q.put(obj, False)。

例項1:

from multiprocessing import Process,Queue

def demo(que):
    print("子程式中:", que.get())  # 獲取到父程式傳遞過來的['a', 'b', 'c']
    for i in range(5):
        que.put(i)

if __name__ == '__main__':
  que = Queue()  # 不指定佇列長度,預設長度無限制
  p1 = Process(target=demo, args=(que,))
  p1.start()
  que.put(['a', 'b', 'c'])  # 在佇列中可以儲存列表,字典等型別。
  p1.join()
  print(que.get())  # 0
  print(que.get())  # 1
  print(que.get())  # 2

例項2:

from multiprocessing import Process,Queue

def read(que):
   print("子程式讀")
   print(que.get())  # [1,2,3,4]

def write(que):
  print("子程式寫")
  que.put([1,2,3,4])


# 兩個子程式之間通過佇列進行資料傳輸
if __name__ == '__main__':
    que = Queue()
    p1 = Process(target=read,args=(que,))
    p2 = Process(target=write,args=(que,))

    p1.start()
    p2.start()
    p1.join()
    p2.join()

共享資料Manage使用代理物件

使用ListProxy

例項1:

from multiprocessing import Manager
manager = Manager()
myList = manager.list([i for i in range(10)])
print(myList)  # <ListProxy object, typeid 'list' at 0x...>
print(str(myList))  # [0, 1, 2, 3, 4, 5,...]
myList.append(100)
print(str(myList))  # [0, 1, 2, 3, 4, 5,...100]

例項2:

from multiprocessing import Manager, Process

def funcList(mylist):
    mylist.append('a')  # 在列表中追加值
    mylist.append('b')

if __name__ == '__main__':
    myList = multiprocessing.Manager().list() 
    myListP = multiprocessing.Process(target=funcList,args=(myList,)
    myListP.start() 
    myListP.join()  # 等待程式執行完畢
    print(myList)  # ['a', 'b']
使用DictProxy

例項:

from multiprocessing import Manager, Process

def funcDict(mydict):
    mydict['name'] = 'zs'
    mydict['age'] = 1

if __name__ == '__main__':
    myDict = multiprocessing.Manager().dict() 
    myDictP = multiprocessing.Process(target=funcDict,args=(myDict,))
    myDictP.start()
    myDictP.join()  # 等待程式執行完畢
    print(myDict)  # {'name': 'zs', 'age': 12}

參考文章連結:
http://www.cnblogs.com/lidagen/p/7252247.html
https://docs.python.org/3.6/library/multiprocessing.html

相關文章