1. 程式人生 > >Python之進程與線程

Python之進程與線程

分配 不能 maxsize hid pan lex 解決 地址空間 對象

一.進程

1.什麽是進程

程序並不能單獨運行,只有將程序裝載到內存中,系統為它分配資源才能運行,而這種執行的程序就稱之為進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。

在多道編程中,我們允許多個程序同時加載到內存中,在操作系統的調度下,可以實現並發地執行。這是這樣的設計,大大提高了CPU的利用率。進程的出現讓每個用戶感覺到自己獨享CPU,因此,進程就是為了在CPU上實現多道編程而提出的。

2.有了進程為什麽還要線程?

進程有很多優點,它提供了多道編程,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機的利用率。很多人就不理解了,既然進程這麽優秀,為什麽還要線程呢?其實,仔細觀察就會發現進程還是有很多缺陷的,主要體現在兩點上:

  • 進程只能在一個時間幹一件事,如果想同時幹兩件事或多件事,進程就無能為力了。

  • 進程在執行的過程中如果阻塞,例如等待輸入,整個進程就會掛起,即使進程中有些工作不依賴於輸入的數據,也將無法執行。

例如,我們在使用qq聊天, qq做為一個獨立進程如果同一時間只能幹一件事,那他如何實現在同一時刻 即能監聽鍵盤輸入、又能監聽其它人給你發的消息、同時還能把別人發的消息顯示在屏幕上呢?你會說,操作系統不是有分時麽?但我的親,分時是指在不同進程間的分時呀, 即操作系統處理一會你的qq任務,又切換到word文檔任務上了,每個cpu時間片分給你的qq程序時,你的qq還是只能同時幹一件事呀。

再直白一點, 一個操作系統就像是一個工廠,工廠裏面有很多個生產車間,不同的車間生產不同的產品,每個車間就相當於一個進程,且你的工廠又窮,供電不足,同一時間只能給一個車間供電,為了能讓所有車間都能同時生產,你的工廠的電工只能給不同的車間分時供電,但是輪到你的qq車間時,發現只有一個幹活的工人,結果生產效率極低,為了解決這個問題,應該怎麽辦呢?。。。。沒錯,你肯定想到了,就是多加幾個工人,讓幾個人工人並行工作,這每個工人,就是線程!

3.

二.線程

1.什麽是線程

  在傳統操作系統中,每個進程有一個地址空間,而且默認就有一個控制線程

  多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間

  進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上的執行單位

例如,北京地鐵與上海地鐵是不同的進程,而北京地鐵裏的13號線是一個線程,北京地鐵所有的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。

2.為什麽要用線程

  多線程指的是,在一個進程中開啟多個線程,簡單的講:如果多個任務共用一塊地址空間,那麽必須在一個進程內開啟多個線程。詳細的講分為4點: 

  1. 多線程共享一個進程的地址空間

  2. 線程比進程更輕量級,線程比進程更容易創建可撤銷,在許多操作系統中,創建一個線程比創建一個進程要快10-100倍,在有大量線程需要動態和快速修改時,這一特性很有用

  3. 若多個線程都是cpu密集型的,那麽並不能獲得性能上的增強,但是如果存在大量的計算和大量的I/O處理,擁有多個線程允許這些活動彼此重疊運行,從而會加快程序執行的速度。

  4. 在多cpu系統中,為了最大限度的利用多核,可以開啟多個線程(比開進程開銷要小的多)

三.Python並發編程之多進程

1.multiprocessing模塊介紹

  python中的多線程無法利用多核優勢,如果想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分情況需要使用多進程。Python提供了非常好用的多進程包multiprocessing。

  multiprocessing模塊用來開啟子進程,並在子進程中執行我們定制的任務(比如函數),該模塊與多線程模塊threading的編程接口類似。

  multiprocessing模塊的功能眾多:支持子進程、通信和共享數據、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。

  需要再次強調的一點是:與線程不同,進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內。

2.Process類的介紹

  • 創建進程的類

Process([group [, target [, name [, args [, kwargs]]]]]),由該類實例化得到的對象,表示一個子進程中的任務(尚未啟動)
強調:
1. 需要使用關鍵字的方式來指定參數
2. args指定的為傳給target函數的位置參數,是一個元組形式,必須有逗號

  • 參數介紹

group參數未使用,值始終為None
target表示調用對象,即子進程要執行的任務
args表示調用對象的位置參數元組,args=(1,2,egon,)
kwargs表示調用對象的字典,kwargs={name:egon,age:18}
name為子進程的名稱
  • 方法介紹

p.start():啟動進程,並調用該子進程中的p.run() 
p.run():進程啟動時運行的方法,正是它去調用target指定的函數,我們自定義類的類中一定要實現該方法  
p.terminate():強制終止進程p,不會進行任何清理操作,如果p創建了子進程,該子進程就成了僵屍進程,使用該方法需要特別小心這種情況。如果p還保存了一個鎖那麽也將不會被釋放,進而導致死鎖
p.is_alive():如果p仍然運行,返回True
p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,需要強調的是,p.join只能join住start開啟的進程,而不能join住run開啟的進程

  • 屬性介紹

p.daemon:默認值為False,如果設為True,代表p為後臺運行的守護進程,當p的父進程終止時,p也隨之終止,並且設定為True後,p不能創建自己的新進程,必須在p.start()之前設置
p.name:進程的名稱
p.pid:進程的pid
p.exitcode:進程在運行時為None、如果為–N,表示被信號N結束(了解即可)
p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。這個鍵的用途是為涉及網絡連接的底層進程間通信提供安全性,這類連接只有在具有相同的身份驗證鍵時才能成功(了解即可)

3.Process類的使用

part1:創建並開啟子進程的方式

註意:在windows中Process()必須放到# if __name__ == ‘__main__‘:下

由於Windows沒有fork,多處理模塊啟動一個新的Python進程並導入調用模塊。
如果在導入時調用Process(),那麽這將啟動無限繼承的新進程(或直到機器耗盡資源)。
這是隱藏對Process()內部調用的原,使用if __name__ == “__main __”,這個if語句中的語句將不會在導入時被調用。

  • 開進程第一種方法

# -*- coding:utf-8 -*-
# /user/bin/python
import time
import random
from multiprocessing import Process
def eat(name):
    print("%s eating" % name)
    time.sleep(random.randint(1,3))
    print("%s eat end" % name)
if __name__ == __main__:

    p1 = Process(target=eat, args=("alex",))
    p2 = Process(target=eat, args=("nickle",))
    p3 = Process(target=eat, args=("json",))
    p4 = Process(target=eat, args=("tom",))

    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print("主線程")

"""
輸出結果:
主線程
alex eating
nickle eating
json eating
tom eating
nickle eat end
json eat end
alex eat end
tom eat end
"""

  • 開啟進程第二種方法

# -*- coding:utf-8 -*-
# /user/bin/python
import time
import random
from multiprocessing import Process
class eat(Process):

    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print("%s eating" % self.name)
        time.sleep(random.randint(1,3))
        print("%s eat end" % self.name)
if __name__ == __main__:
    p1 = eat("alex")
    p2 = eat("nickle")
    p3 = eat("json")
    p4 = eat("tom")

    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print("主進程")

"""
輸出結果:
主進程
alex eating
json eating
nickle eating
tom eating
nickle eat end
tom eat end
json eat end
alex eat end
"""

part2:Process對象的其他方法或屬性

  • 進程對象的方法一:terminate,is_alive

# -*- coding:utf-8 -*-
# /user/bin/python
from multiprocessing import Process
import time
import random
class eat(Process):

    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print("%s eating" % self.name)
        time.sleep(random.randint(1,3))
        print("%s eat end" % self.name)
if __name__ == __main__:
    p = eat("nickle")
    p.start()

    p.terminate() # 關閉進程,不會立即關閉,所以is_alive立刻查看的結果可能還是存活
    print(p.is_alive()) # 結果為True
    time.sleep(random.randint(1,3))
    print("go")
    print(p.is_alive())  # 結果為False

"""
輸出結果:
True
go
False
"""

註意了:p.join(),是父進程在等p的結束,是父進程阻塞在原地,而p仍然在後臺運行

  • 進程對象的方法二:p.daemon=True,p.join

# -*- coding:utf-8 -*-
# /user/bin/python
from multiprocessing import Process
import time
import random
class eat(Process):

    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print("%s eating" % self.name)
        time.sleep(random.randint(1,3))
        print("%s eat end" % self.name)
if __name__ == __main__:
    p = eat("nickle")
    p.daemon = True # 一定要在p.start()前設置,設置p為守護進程,禁止p創建子進程,並且主進程死,p跟著一起死
    p.start()

    p.join(0.0001) # 等待p停止,等0.0001秒就不再等了
    print("go")

"""
輸出結果:
go
"""
技術分享
# -*- coding:utf-8 -*-
# /user/bin/python
from multiprocessing import Process
import time
import random

def eat(name):
    print("%s eating" % name)
    time.sleep(random.randint(1,3))
    print("%s eat end" % name)
if __name__ == __main__:

    p1 = Process(target=eat, args=("alex",))
    p2 = Process(target=eat, args=("nickle",))
    p3 = Process(target=eat, args=("json",))
    p4 = Process(target=eat, args=("tom",))

    p1.start()
    p2.start()
    p3.start()
    p4.start()

    # 既然join是等待進程結束,那麽我像下面這樣寫,進程不就又變成串行的了嗎?
    # 當然不是了,必須明確:p.join()是讓誰等?
    # 很明顯p.join()是讓主線程等待p的結束,卡住的是主線程而絕非進程p,

    # 詳細解析如下:
    # 進程只要start就會在開始運行了,所以p1-p4.start()時,系統中已經有四個並發的進程了
    # 而我們p1.join()是在等p1結束,沒錯p1只要不結束主線程就會一直卡在原地,這也是問題的關鍵
    # join是讓主線程等,而p1-p4仍然是並發執行的,p1.join的時候,其余p2,p3,p4仍然在運行,等#p1.join結束,可能p2,p3,p4早已經結束了,這樣p2.join,p3.join.p4.join直接通過檢測,無需等待
    # 所以4個join花費的總時間仍然是耗費時間最長的那個進程運行的時間

    p1.join()
    p2.join()
    p3.join()
    p4.join()
    print("主線程")
# 簡化方式:
#     p_lis = [p1, p2, p3, p4]
#     for p in p_lis:
#         p.start()
#
#     for p in p_lis:
#         p.join()
"""
輸出結果:
alex eating
nickle eating
json eating
tom eating
nickle eat end
tom eat end
alex eat end
json eat end
主線程
"""
View Code

  • 進程對象的其他方法:name,id

# -*- coding:utf-8 -*-
# /user/bin/python
from multiprocessing import Process
import time
import random

class eat(Process):

    def __init__(self,name):
        # self.name=name
        # super().__init__() #Process的__init__方法會執行self.name=Piao-1,
        #                    #所以加到這裏,會覆蓋我們的self.name=name

        #為我們開啟的進程設置名字的做法
        super().__init__()
        self.name = name

    def run(self):
        print("%s eating" % self.name)
        time.sleep(random.randint(1,3))
        print("%s eat end" % self.name)
if __name__ == __main__:

    p = eat("nickle")
    p.start()
    print("主線程")
    print(p.pid)
    print(p.name)

"""
輸出結果:
主線程
9100
nickle
nickle eating
nickle eat end
"""

4.進程間通信(IPC)方式一:隊列

進程彼此之間互相隔離,要實現進程間通信,即IPC,multiprocessing模塊支持兩種形式:隊列和管道,這兩種方式都是使用消息傳遞的

  創建隊列的類(底層就是以管道和鎖定的方式實現):

1 Queue([maxsize]):創建共享的進程隊列,Queue是多進程安全的隊列,可以使用Queue實現多進程之間的數據傳遞。

  參數介紹:

主要方法:

1 q.put方法用以插入數據到隊列中,put方法還有兩個可選參數:blocked和timeout。如果blocked為True(默認值),並且timeout為正值,該方法會阻塞timeout指定的時間,直到該隊列有剩余的空間。
如果超時,會拋出Queue.Full異常。如果blocked為False,但該Queue已滿,會立即拋出Queue.Full異常。
2 q.get方法可以從隊列讀取並且刪除一個元素。同樣,get方法有兩個可選參數:blocked和timeout。如果blocked為True(默認值),並且timeout為正值,那麽在等待時間內沒有取到任何元素,
會拋出Queue.Empty異常。如果blocked為False,有兩種情況存在,如果Queue有一個值可用,則立即返回該值,否則,如果隊列為空,則立即拋出Queue.Empty異常.
3 4 q.get_nowait():同q.get(False) 5 q.put_nowait():同q.put(False) 6 7 q.empty():調用此方法時q為空則返回True,該結果不可靠,比如在返回True的過程中,如果隊列中又加入了項目。 8 q.full():調用此方法時q已滿則返回True,該結果不可靠,比如在返回True的過程中,如果隊列中的項目被取走。 9 q.qsize():返回隊列中目前項目的正確數量,結果也不可靠,理由同q.empty()和q.full()一樣

其他方法:

1 1 q.cancel_join_thread():不會在進程退出時自動連接後臺線程。可以防止join_thread()方法阻塞
2 2 q.close():關閉隊列,防止隊列中加入更多數據。調用此方法,後臺線程將繼續寫入那些已經入隊列但尚未寫入的數據,但將在此方法完成時馬上關閉。如果q被垃圾收集,將調用此方法。關閉隊列不會在隊列
使用者中產生任何類型的數據結束信號或異常。例如,如果某個使用者正在被阻塞在get()操作上,關閉生產者中的隊列不會導致get()方法返回錯誤。
3 3 q.join_thread():連接隊列的後臺線程。此方法用於在調用q.close()方法之後,等待所有隊列項被消耗。默認情況下,此方法由不是q的原始創建者的所有進程調用。調用q.cancel_join_thread方法可以禁止這種行為

應用:

Python之進程與線程