[018]Python併發編程_基礎_全棧基礎

您好!此筆記的文本和代碼以網盤形式分享於文末!

因個人能力有限,錯誤處歡迎大家交流和指正!基礎部分內容簡單,但多且零散!

"""

# 操作系統

批處理系統:在它的控制下,計算機能夠自動地、成批地處理一個或多個用戶的作業

        (這作業包括程序、數據和命令)

多道程序系統:允許多個程序同時進入內存並運行,多道程序系統的出現,

    標誌着操作系統漸趨成熟的階段,先後出現了作業調度管理、處理機管理、存儲器管理、

    外部設備管理、文件系統管理等功能。同時也開始有了空間隔離和時空複用的概念特點,

分時系統:多用戶分時系統是當今計算機操作系統中最普遍使用的一類操作系統,

    分時——現在流行的PC,服務器都是採用這種運行模式

實時系統:實時控制系統、實時信息處理系統。

    及時響應。每一個信息接收、分析處理和發送的過程必須在嚴格的時間限制內完成。

    高可靠性。需採取冗餘措施,雙機系統前後臺工作,也包括必要的保密措施等。

    實時——一般用於單片機上、PLC等

通用操作系統:

    通用操作系統:具有多種類型操作特徵的操作系統。

    可以同時兼有多道批處理、分時、實時處理的功能,或其中兩種以上的功能。

 

現代操作系統:

個人計算機操作系統:個人計算機上的操作系統是聯機交互的單用戶操作系統

網絡操作系統:在原來各自計算機操作系統上,按照網絡體系結構的各個協議標準增加

    網絡管理模塊,其中包括:通信、資源共享、系統安全和各種網絡應用服務。

分佈式操作系統:分佈式操作系統也是通過通信網絡,將地理上分散的具有自治功能的

    數據處理系統或計算機系統互連起來,實現信息交換和資源共享,協作完成任務。

    分佈式系統更強調分佈式計算和處理。

 

操作系統就是一個協調、管理和控制計算機硬件資源和軟件資源的控制程序。

一:隱藏了醜陋的硬件調用接口,爲應用程序員提供調用硬件資源的更好,

更簡單,更清晰的模型(系統調用接口)。應用程序員有了這些接口後,

就不用再考慮操作硬件的細節,專心開發自己的應用程序即可。

二:將應用程序對硬件資源的競態請求變得有序化

 

"""

 

"""

# 進程

進程:進程即正在執行的一個過程,進程是對正在運行程序的一個抽象。是操作系統最核心的概念

   操作系統的其他所有內容都是圍繞進程的概念展開的。

 

#一 操作系統的作用:

    1:隱藏醜陋複雜的硬件接口,提供良好的抽象接口

    2:管理、調度進程,並且將多個進程對硬件的競爭變得有序

 

#二 多道技術:

    1.產生背景:針對單核,實現併發

    ps:

    現在的主機一般是多核,那麼每個核都會利用多道技術

    有4個cpu,運行於cpu1的某個程序遇到io阻塞,會等到io結束再重新調度,會被調度到4個

    cpu中的任意一個,具體由操作系統調度算法決定。

   

    2.空間上的複用:如內存中同時有多道程序

    3.時間上的複用:複用一個cpu的時間片

       強調:遇到io切,佔用cpu時間過長也切,核心在於切之前將進程的狀態保存下來,這樣

            才能保證下次切換回來時,能基於上次切走的位置繼續運行

 

進程(process)是計算機中的程序關於某數據集合上的一次運行活動,

    是系統進行資源分配和調度的基本單位,是操作系統結構的基礎.

    程序是指令、數據及其組織形式的描述,進程是程序的實體.

    狹義上進程是正在運行的程序的實例,

    廣義上它是操作系統動態執行的基本單元,在傳統的操作系統中,

    進程既是基本的分配單元,也是基本的執行單元。

 

進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,

包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。

文本區域存儲處理器執行的代碼;

數據區域存儲變量和進程執行期間使用的動態分配的內存;

堆棧區域存儲着活動過程調用的指令和本地變量。

進程是一個“執行中的程序”。程序是一個沒有生命的實體,只有處理器賦予程序生命時

(即被操作系統執行),它才能成爲一個活動的實體,我們稱其爲進程。

 

從實現角度,進程是一種數據結構,目的在於清晰地刻畫動態系統的內在規律,

    有效管理和調度進入計算機系統主存儲器運行的程序。

   

進程的特徵:

動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。

併發性:任何進程都可以同其他進程一起併發執行

獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;

異步性:由於進程間的相互制約,使進程具有執行的間斷性,即進程按各自獨立的、

        不可預知的速度向前推進。

結構特徵:進程由程序、數據和進程控制塊三部分組成。

多個不同的進程可以包含相同的程序:一個程序在不同的數據集裏就構成不同的進程,

    能得到不同的結果;但是執行過程中,程序不能發生改變。

同一個程序執行兩次,就會在操作系統中出現兩個進程,

    所以我們可以同時運行一個軟件,分別做不同的事情也不會混亂。

   

進程的調度(進程的調度算法)

1、先來先服務(FCFS)調度算法是一種最簡單的調度算法,該算法既可用於作業調度,

也可用於進程調度。FCFS算法比較有利於長作業(進程),而不利於短作業(進程)。

由此可知,本算法適合於CPU繁忙型作業,而不利於I/O繁忙型的作業(進程)。

2、短作業(進程)優先調度算法(SJ/PF)是指對短作業或短進程優先調度的算法,

該算法既可用於作業調度,也可用於進程調度。但其對長作業不利;

不能保證緊迫性作業(進程)被及時處理;作業的長短只是被估算出來的。

3、時間片輪轉(Round Robin,RR)法的基本思路是讓每個進程在就緒隊列中的等待時間

與享受服務的時間成比例。在時間片輪轉法中,需要將CPU的處理時間分成固定大小的時間片。

4、多級反饋隊列調度算法則不必事先知道各種進程所需的執行時間,

而且還可以滿足各種類型進程的需要,因而它是目前被公認的一種較好的進程調度算法。

    <1>設置多個就緒隊列,併爲各個隊列賦予不同的優先級。

    <2>一個新進程進入內存後,首先將它放入第一隊列的末尾,按FCFS原則排隊等待調度。

    <3>僅當第一隊列空閒時,調度程序才調度第二隊列中的進程運行;

 

進程的並行和併發

並行 : 並行是指兩者同時執行

併發 : 併發是指資源有限的情況下,兩者交替輪流使用資源

 

由於操作系統的調度算法

程序會進入的狀態:就緒,運行和阻塞

1、就緒(Ready)狀態

當進程已分配到除CPU以外的所有必要的資源,只要獲得處理機便可立即執行

2、執行/運行(Running)狀態

當進程已獲得處理機,其程序正在處理機上執行。

3、阻塞(Blocked)狀態(成因:等待I/O完成、申請緩衝區不能滿足、等待信件(信號)等)

正在執行的進程,由於等待某個事件發生而無法執行時,便放棄處理機而處於阻塞狀態

 

同步:同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,

    依賴的任務才能算完成,這是一種可靠的任務序列。要麼成功都成功,失敗都失敗,

    兩個任務的狀態可以保持一致。

異步:異步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作,

    依賴的任務也立即執行,只要自己完成了整個任務就算完成了。

    至於被依賴的任務最終是否真正完成,依賴它的任務無法確定,

    所以它是不可靠的任務序列。

 

阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。

 

1、同步阻塞形式

  效率最低。拿上面的例子來說,就是你專心排隊,什麼別的事都不做。

2、異步阻塞形式

  如果在銀行等待辦理業務的人採用的是異步的方式去等待消息被觸發(通知),

    也就是領了一張小紙條,假如在這段時間裏他不能離開銀行做其它的事情,那麼很顯然,

    這個人被阻塞在了這個等待的操作上面;異步操作是可以被阻塞住的,

    只不過它不是在處理消息時阻塞,而是在等待消息通知時被阻塞。

3、同步非阻塞形式

  實際上是效率低下的。想象一下你一邊打着電話一邊還需要擡頭看到底隊伍排到你了沒有,

    如果把打電話和觀察排隊的位置看成是程序的兩個操作的話,

    這個程序需要在這兩種不同的行爲之間來回的切換,效率可想而知是低下的。

4、異步非阻塞形式(*****)

  效率更高,因爲打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,

    程序沒有在兩種不同的操作中來回切換。比如說,這個人突然發覺自己煙癮犯了,

    需要出去抽根菸,於是他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下,

    那麼他就沒有被阻塞在這個等待的操作上面,自然這個就是異步+非阻塞的方式了。

 

通用系統的進程創建形式

1. 系統初始化(查看進程linux中用ps命令,windows中用任務管理器,

    前臺進程負責與用戶交互,後臺運行的進程與用戶無關,

    運行在後臺並且只在需要時才喚醒的進程,稱爲守護進程,

    如電子郵件、web頁面、新聞、打印)

2. 一個進程在運行過程中開啓了子進程(如nginx開啓多進程os.fork,subprocess.Popen等)

3. 用戶的交互式請求,而創建一個新進程(如用戶雙擊暴風影音)

4. 一個批處理作業的初始化(只在大型機的批處理系統中應用。

 

進程的結束

1. 正常退出(自願,如用戶點擊交互式頁面的叉號,或程序執行完畢調用發起系統調用正常退出,

    在linux中用exit,在windows中用ExitProcess)

2. 出錯退出(自願,python a.py中a.py不存在)

3. 嚴重錯誤(非自願,執行非法指令,如引用不存在的內存,1/0等,可以捕捉異常,

    try...except...)

4. 被其他進程殺死(非自願,如kill -9)

"""

"""

multiprocess模塊:不是一個模塊而是python中一個操作、管理進程的包.

提供了創建進程部分,進程同步部分,進程池部分,進程之間數據共享功能的大量模塊。

process模塊是一個創建進程的模塊,藉助這個模塊,就可以完成進程的創建。

Process([group [, target [, name [, args [, kwargs]]]]]),

    由該類實例化得到的對象,表示一個子進程中的任務(尚未啓動)

注意:

1. 需要使用關鍵字的方式來指定參數。

2. args指定的爲傳給target函數的位置參數,是一個元組形式,必須有逗號。

參數介紹:

1 group參數未使用,值始終爲None

2 target表示調用對象,即子進程要執行的任務

3 args表示調用對象的位置參數元組,args=(1,2,'egon',)

4 kwargs表示調用對象的字典,kwargs={'name':'egon','age':18}

5 name爲子進程的名稱

方法介紹:

1 p.start():啓動進程,並調用該子進程中的p.run()

2 p.run():進程啓動時運行的方法,正是它去調用target指定的函數,

    我們自定義類的類中一定要實現該方法。 

3 p.terminate():強制終止進程p,不會進行任何清理操作,如果p創建了子進程,

    該子進程就成了殭屍進程,使用該方法需要特別小心這種情況。

    如果p還保存了一個鎖那麼也將不會被釋放,進而導致死鎖

4 p.is_alive():如果p仍然運行,返回True

5 p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,

    而p是處於運行的狀態)。timeout是可選的超時時間,需要強調的是,

    p.join只能join住start開啓的進程,而不能join住run開啓的進程

屬性介紹:

1 p.daemon:默認值爲False,如果設爲True,代表p爲後臺運行的守護進程,

    當p的父進程終止時,p也隨之終止,並且設定爲True後,p不能創建自己的新進程,

    必須在p.start()之前設置

2 p.name:進程的名稱

3 p.pid:進程的pid

4 p.exitcode:進程在運行時爲None、如果爲–N,表示被信號N結束(瞭解即可)

5 p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。

    這個鍵的用途是爲涉及網絡連接的底層進程間通信提供安全性,

    這類連接只有在具有相同的身份驗證鍵時才能成功(瞭解即可)

# 使用process模塊創建進程:

import time

from multiprocessing import Process

 

def f(name):

    print('hello', name)

    print('我是子進程')

 

if __name__ == '__main__':

    p = Process(target=f, args=('bob',))

    p.start()

    time.sleep(1)

    print('執行主進程的內容了')

 

#  join 方法

import time

from multiprocessing import Process

 

def f(name):

    print('hello', name)

    time.sleep(1)

    print('我是子進程')

 

 

if __name__ == '__main__':

    p = Process(target=f, args=('bob',))

    p.start()

    #p.join()

    print('我是父進程')

 

# 查看主進程和子進程的進程號

import os

from multiprocessing import Process

 

def f(x):

    print('子進程id :',os.getpid(),'父進程id :',os.getppid())

    return x*x

 

if __name__ == '__main__':

    print('主進程id :', os.getpid())

    p_lst = []

    for i in range(5):

        p = Process(target=f, args=(i,))

        p.start()

 

# 多個進程同時運行

import time

from multiprocessing import Process

 

 

def f(name):

    print('hello', name)

    time.sleep(1)

 

 

if __name__ == '__main__':

    p_lst = []

    for i in range(5):

        p = Process(target=f, args=('bob',))

        p.start()

        p_lst.append(p)

 

# 多個進程同時運行,join方法1

import time

from multiprocessing import Process

 

 

def f(name):

    print('hello', name)

    time.sleep(1)

 

 

if __name__ == '__main__':

    p_lst = []

    for i in range(5):

        p = Process(target=f, args=('bob',))

        p.start()

        p_lst.append(p)

        p.join()

    # [p.join() for p in p_lst]

    print('父進程在執行')

 

# 多個進程同時運行,join方法2

import time

from multiprocessing import Process

 

def f(name):

    print('hello', name)

    time.sleep(1)

 

if __name__ == '__main__':

    p_lst = []

    for i in range(5):

        p = Process(target=f, args=('bob',))

        p.start()

        p_lst.append(p)

    # [p.join() for p in p_lst]

    print('父進程在執行')

 

# 通過繼承Process類開啓進程

import os

from multiprocessing import Process

 

 

class MyProcess(Process):

    def __init__(self,name):

        super().__init__()

        self.name=name

    def run(self):

        print(os.getpid())

        print('%s 正在和女主播聊天' %self.name)

 

p1=MyProcess('wupeiqi')

p2=MyProcess('yuanhao')

p3=MyProcess('nezha')

 

p1.start() #start會自動調用run

p2.start()

# p2.run()

p3.start()

 

 

p1.join()

p2.join()

p3.join()

 

print('主線程')

 

# 進程之間的數據隔離問題

from multiprocessing import Process

 

def work():

    global n

    n=0

    print('子進程內: ',n)

 

 

if __name__ == '__main__':

    n = 100

    p=Process(target=work)

    p.start()

    print('主進程內: ',n)

 

# 守護進程--會隨着主進程的結束而結束。

# 守護進程內無法再開啓子進程,否則拋出異常:AssertionError

# 守護進程的啓動

import os

import time

from multiprocessing import Process

 

class Myprocess(Process):

    def __init__(self, person):

        super().__init__()

        self.person = person

    def run(self):

        print(os.getpid(), self.name)

        print('%s正在和女主播聊天' % self.person)

 

 

p = Myprocess('哪吒')

# 一定要在p.start()前設置,設置p爲守護進程,禁止p創建子進程,

# 並且父進程代碼執行結束,p即終止運行

p.daemon = True

p.start()

# 在sleep時查看進程id對應的進程ps -ef|grep id

time.sleep(10)

print('主')

 

# 主進程代碼執行結束守護進程立即結束

from multiprocessing import Process

 

def foo():

    print(123)

    time.sleep(1)

    print("end123")

 

def bar():

    print(456)

    time.sleep(3)

    print("end456")

 

 

p1 = Process(target=foo)

p2 = Process(target=bar)

 

p1.daemon=True

p1.start()

p2.start()

time.sleep(0.1)

print("main-------")

 

"""

 

"""

# socket聊天併發實例

# server端

from socket import *

from multiprocessing import Process

 

server=socket(AF_INET, SOCK_STREAM)

server.setsockopt(SOL_SOCKET, SO_REUSEADDR,1)

server.bind(('127.0.0.1', 8080))

server.listen(5)

 

def talk(conn,client_addr):

    while True:

        try:

            msg = conn.recv(1024)

            if not msg:break

            conn.send(msg.upper())

        except Exception:

            break

 

# windows下start進程一定要寫到這下面

if __name__ == '__main__':

    while True:

        conn, client_addr = server.accept()

        p=Process(target=talk, args=(conn, client_addr))

        p.start()

       

# client端

from socket import *

 

client = socket(AF_INET, SOCK_STREAM)

client.connect(('127.0.0.1', 8080))

 

 

while True:

    msg=input('>>: ').strip()

    if not msg:continue

 

    client.send(msg.encode('utf-8'))

    msg=client.recv(1024)

    print(msg.decode('utf-8'))

      

"""

 

"""

# 進程對象的其他方法:terminate,is_alive

from multiprocessing import Process

import time

import random

 

class Myprocess(Process):

    def __init__(self, person):

        self.name = person

        super().__init__()

 

    def run(self):

        print('%s正在和網紅臉聊天' % self.name)

        time.sleep(random.randrange(1, 5))

        print('%s還在和網紅臉聊天' %self.name)

 

 

p1=Myprocess('哪吒')

p1.start()

 

p1.terminate()#關閉進程,不會立即關閉,所以is_alive立刻查看的結果可能還是存活

print(p1.is_alive()) #結果爲True

 

print('開始')

print(p1.is_alive()) #結果爲False

 

# 進程對象的其他屬性:pid和name

class Myprocess(Process):

    def __init__(self, person):

        # name屬性是Process中的屬性,標示進程的名字

        self.name=person

        # 執行父類的初始化方法會覆蓋name屬性

        super().__init__()

        # 在這裏設置就可以修改進程名字了

        # self.name = person

        # 如果不想覆蓋進程名,就修改屬性名稱就可以了

        # self.person = person

    def run(self):

        print('%s正在和網紅臉聊天' %self.name)

        # print('%s正在和網紅臉聊天' %self.person)

        time.sleep(random.randrange(1,5))

        print('%s正在和網紅臉聊天' %self.name)

        # print('%s正在和網紅臉聊天' %self.person)

 

 

p1 = Myprocess('哪吒')

p1.start()

# 可以查看子進程的進程id

print(p1.pid)

 

"""

 

"""

進程同步(multiprocess.Lock) -- 鎖 —— multiprocess.Lock

併發編程能更加充分的利用IO資源,多個進程運行無順序,一旦開啓不受控制,

當多個進程使用同一份數據資源時,會引發數據安全或順序混亂問題。

# 多進程搶佔輸出資源

import os

import time

import random

from multiprocessing import Process

 

def work(n):

    print('%s: %s is running' %(n,os.getpid()))

    time.sleep(random.random())

    print('%s:%s is done' %(n,os.getpid()))

 

if __name__ == '__main__':

    for i in range(3):

        p=Process(target=work,args=(i,))

        p.start()

 

# 使用鎖維護執行順序

# 由併發變成了串行,犧牲了運行效率,但避免了競爭

import os

import time

import random

from multiprocessing import Process,Lock

 

def work(lock,n):

    lock.acquire()

    print('%s: %s is running' % (n, os.getpid()))

    time.sleep(random.random())

    print('%s: %s is done' % (n, os.getpid()))

    lock.release()

if __name__ == '__main__':

    lock=Lock()

    for i in range(3):

        p=Process(target=work,args=(lock,i))

        p.start()

 

# 模擬多進程同時搶票

#文件db的內容爲:{"count":1}

#注意一定要用雙引號,不然json無法識別

#併發運行,效率高,但競爭寫同一文件,數據寫入錯亂

from multiprocessing import Process,Lock

import time,json,random

def search():

    dic=json.load(open('db'))

    print('\033[43m剩餘票數%s\033[0m' % dic['count'])

 

def get():

    dic=json.load(open('db'))

    # 模擬讀數據的網絡延遲

    time.sleep(0.1)

    if dic['count'] > 0:

        dic['count'] -= 1

        # 模擬寫數據的網絡延遲

        time.sleep(0.2)

        json.dump(dic,open('db', 'w'))

        print('\033[43m購票成功\033[0m')

 

def task():

    search()

    get()

 

if __name__ == '__main__':

    # 模擬併發100個客戶端搶票

    for i in range(100):

        p=Process(target=task)

        p.start()

 

# 使用鎖來保證數據安全

#文件db的內容爲:{"count":5}

#注意一定要用雙引號,不然json無法識別

#併發運行,效率高,但競爭寫同一文件,數據寫入錯亂

from multiprocessing import Process,Lock

import time,json,random

def search():

    dic=json.load(open('db'))

    print('\033[43m剩餘票數%s\033[0m' %dic['count'])

 

def get():

    dic=json.load(open('db'))

    time.sleep(random.random()) #模擬讀數據的網絡延遲

    if dic['count'] >0:

        dic['count']-=1

        time.sleep(random.random()) #模擬寫數據的網絡延遲

        json.dump(dic,open('db','w'))

        print('\033[32m購票成功\033[0m')

    else:

        print('\033[31m購票失敗\033[0m')

 

def task(lock):

    search()

    lock.acquire()

    get()

    lock.release()

 

if __name__ == '__main__':

    lock = Lock()

    for i in range(100): #模擬併發100個客戶端搶票

        p=Process(target=task,args=(lock,))

        p.start()

 

加鎖可以保證多個進程修改同一塊數據時,同一時間只能有一個任務可以進行修改,

即串行的修改,沒錯,速度是慢了,但犧牲了速度卻保證了數據安全.

加鎖的問題:

1.效率低(共享數據基於文件,而文件是硬盤上的數據)

2.需要自己加鎖處理

 

"""

 

"""

進程間通信IPC(Inter-Process Communication)——隊列(multiprocess.Queue)

 

解決方案:1、效率高;2、幫助處理好鎖的問題

mutiprocessing模塊爲我們提供的基於消息的IPC通信機制:隊列和管道

隊列和管道都是將數據存放於內存中,

隊列又是基於(管道+鎖)實現的,可以讓我們從複雜的鎖問題中解脫出來,

我們應該儘量避免使用共享數據,儘可能使用消息傳遞和隊列,

避免處理複雜的同步和鎖問題,而且在進程數目增多時,往往可以獲得更好的可獲展性。

 

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

 

Queue([maxsize])

創建共享的進程隊列。

參數 :maxsize是隊列中允許的最大項數。如果省略此參數,則無大小限制。

底層隊列使用管道和鎖定實現。還需要運行支持線程以便隊列中的數據傳輸到底層管道中。

 

方法介紹:

q.get( [ block [ ,timeout ] ] )

    返回q中的一個項目。如果q爲空,此方法將阻塞,直到隊列中有項目可用爲止。

    block用於控制阻塞行爲,默認爲True. 如果設置爲False,

    將引發Queue.Empty異常(定義在Queue模塊中)。timeout是可選超時時間,用在阻塞模式中。

    如果在制定的時間間隔內沒有項目變爲可用,將引發Queue.Empty異常。

q.get_nowait( )

    同q.get(False)方法。

q.put(item [, block [,timeout ] ] )

    將item放入隊列。如果隊列已滿,此方法將阻塞至有空間可用爲止。block控制阻塞行爲,

    默認爲True。如果設置爲False,將引發Queue.Empty異常(定義在Queue庫模塊中)。

    timeout指定在阻塞模式中等待可用空間的時間長短。超時後將引發Queue.Full異常。

q.qsize()

    返回隊列中目前項目的正確數量。此函數的結果並不可靠,

    因爲在返回結果和在稍後程序中使用結果之間,隊列中可能添加或刪除了項目。

    在某些系統上,此方法可能引發NotImplementedError異常。

q.empty()

    如果調用此方法時 q爲空,返回True。如果其他進程或線程正在往隊列中添加項目,

    結果是不可靠的。也就是說,在返回和使用結果之間,隊列中可能已經加入新的項目。

q.full()

    如果q已滿,返回爲True. 由於線程的存在,結果也可能是不可靠的(參考q.empty()方法)

 

其他了解方法:

q.close()

    關閉隊列,防止隊列中加入更多數據。調用此方法時,後臺線程將繼續寫入那些已入隊列

    但尚未寫入的數據,但將在此方法完成時馬上關閉。如果q被垃圾收集,將自動調用此方法。

    關閉隊列不會在隊列使用者中生成任何類型的數據結束信號或異常。例如,

    如果某個使用者正被阻塞在get()操作上,關閉生產者中的隊列不會導致get()方法返回錯誤。

q.cancel_join_thread()

    不會再進程退出時自動連接後臺線程。這可以防止join_thread()方法阻塞。

q.join_thread()

    連接隊列的後臺線程。此方法用於在調用q.close()方法後,等待所有隊列項被消耗。

    默認情況下,此方法由不是q的原始創建者的所有進程調用。

    調用q.cancel_join_thread()方法可以禁止這種行爲。

 

multiprocessing模塊支持進程間通信的兩種主要形式:管道和隊列

都是基於消息傳遞實現的,但是隊列接口

 

# 隊列基本用法

from multiprocessing import Queue

q=Queue(3)

 

#put ,get ,put_nowait,get_nowait,full,empty

q.put(3)

q.put(3)

q.put(3)

# # 如果隊列已經滿了,程序就會停在這裏,等待數據被別人取走,再將數據放入隊列。

# # 如果隊列中的數據一直不被取走,程序就會永遠停在這裏。

# q.put(3)

 

try:

    # 可以使用put_nowait,如果隊列滿了不會阻塞,但是會因爲隊列滿了而報錯。

    q.put_nowait(3)

except:

    # 因此我們可以用一個try語句來處理這個錯誤。

    # 這樣程序不會一直阻塞下去,但是會丟掉這個消息。

    print('隊列已經滿了')

 

# 因此,我們再放入數據之前,可以先看一下隊列的狀態,如果已經滿了,就不繼續put了。

print(q.full())

 

print(q.get())

print(q.get())

print(q.get())

# # 同put方法一樣,如果隊列已經空了,那麼繼續取就會出現阻塞。

# print(q.get())

try:

    # 可以使用get_nowait,如果隊列滿了不會阻塞,但是會因爲沒取到值而報錯。

    q.get_nowait(3)

except:

    # 因此我們可以用一個try語句來處理這個錯誤。這樣程序不會一直阻塞下去。

    print('隊列已經空了')

 

print(q.empty()) #空了

 

# 子進程發送數據給父進程

import time

from multiprocessing import Process, Queue

 

def f(q):

    q.put([time.asctime(), 'from Eva', 'hello'])  #調用主函數中p進程傳遞過來的進程參數 put函數爲向隊列中添加一條數據。

 

if __name__ == '__main__':

    q = Queue() #創建一個Queue對象

    p = Process(target=f, args=(q,)) #創建一個進程

    p.start()

    print(q.get())

    p.join()

 

# 批量生產數據放入隊列再批量獲取結果 x

import os

import time

import multiprocessing

 

# 向queue中輸入數據的函數

def inputQ(queue):

    info = str(os.getpid()) + '(put):' + str(time.asctime())

    queue.put(info)

 

# 向queue中輸出數據的函數

def outputQ(queue):

    info = queue.get()

    print ('%s%s\033[32m%s\033[0m'%(str(os.getpid()), '(get):',info))

 

# Main

if __name__ == '__main__':

    multiprocessing.freeze_support()

    record1 = []   # store input processes

    record2 = []   # store output processes

    queue = multiprocessing.Queue(3)

 

    # 輸入進程

    for i in range(10):

        process = multiprocessing.Process(target=inputQ,args=(queue,))

        process.start()

        record1.append(process)

 

    # 輸出進程

    for i in range(10):

        process = multiprocessing.Process(target=outputQ,args=(queue,))

        process.start()

        record2.append(process)

 

    for p in record1:

        p.join()

 

    for p in record2:

        p.join()

 

"""

 

"""

# 生產者消費者模型

生產者就是生產數據的線程,消費者就是消費數據的線程。

生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。

生產者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,

所以生產者生產完數據之後不用等待消費者處理,直接扔給阻塞隊列,

消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就相當於一個緩衝區,

平衡了生產者和消費者的處理能力。

 

# 基於隊列實現生產者消費者模型

from multiprocessing import Process,Queue

import time,random,os

def consumer(q):

    while True:

        res=q.get()

        time.sleep(random.randint(1,3))

        print('\033[45m%s 喫 %s\033[0m' %(os.getpid(),res))

 

def producer(q):

    for i in range(10):

        time.sleep(random.randint(1,3))

        res='包子%s' %i

        q.put(res)

        print('\033[44m%s 生產了 %s\033[0m' %(os.getpid(),res))

 

if __name__ == '__main__':

    q=Queue()

    #生產者們:即廚師們

    p1=Process(target=producer,args=(q,))

 

    #消費者們:即喫貨們

    c1=Process(target=consumer,args=(q,))

 

    #開始

    p1.start()

    c1.start()

    print('主')

 

# 解決q.get()死循環問題

from multiprocessing import Process,Queue

import time,random,os

def consumer(q):

    while True:

        res=q.get()

        if res is None:break #收到結束信號則結束

        time.sleep(random.randint(1, 3))

        print('\033[45m%s 喫 %s\033[0m' %(os.getpid(), res))

 

def producer(q):

    for i in range(10):

        time.sleep(random.randint(1, 3))

        res='包子%s' %i

        q.put(res)

        print('\033[44m%s 生產了 %s\033[0m' %(os.getpid(), res))

    # 發送結束信號

    q.put(None)

if __name__ == '__main__':

    q=Queue()

    #生產者們:即廚師們

    p1=Process(target=producer, args=(q,))

 

    #消費者們:即喫貨們

    c1=Process(target=consumer, args=(q,))

 

    #開始

    p1.start()

    c1.start()

    print('主')

 

# 主進程在生產者生產完畢後發送結束信號None

from multiprocessing import Process,Queue

import time,random,os

def consumer(q):

    while True:

        res=q.get()

        if res is None:break #收到結束信號則結束

        time.sleep(random.randint(1,3))

        print('\033[45m%s 喫 %s\033[0m' %(os.getpid(),res))

 

def producer(q):

    for i in range(2):

        time.sleep(random.randint(1,3))

        res='包子%s' %i

        q.put(res)

        print('\033[44m%s 生產了 %s\033[0m' %(os.getpid(),res))

 

if __name__ == '__main__':

    q=Queue()

    #生產者們:即廚師們

    p1=Process(target=producer,args=(q,))

 

    #消費者們:即喫貨們

    c1=Process(target=consumer,args=(q,))

 

    #開始

    p1.start()

    c1.start()

 

    p1.join()

    q.put(None) #發送結束信號

    print('主')

   

# 多個消費者的例子:有幾個消費者就需要發送幾次結束信號

from multiprocessing import Process,Queue

import time,random,os

def consumer(q):

    while True:

        res=q.get()

        if res is None:break #收到結束信號則結束

        time.sleep(random.randint(1,3))

        print('\033[45m%s 喫 %s\033[0m' %(os.getpid(),res))

 

def producer(name,q):

    for i in range(2):

        time.sleep(random.randint(1,3))

        res='%s%s' %(name,i)

        q.put(res)

        print('\033[44m%s 生產了 %s\033[0m' %(os.getpid(),res))

 

if __name__ == '__main__':

    q=Queue()

    #生產者們:即廚師們

    p1=Process(target=producer,args=('包子',q))

    p2=Process(target=producer,args=('骨頭',q))

    p3=Process(target=producer,args=('泔水',q))

 

    #消費者們:即喫貨們

    c1=Process(target=consumer,args=(q,))

    c2=Process(target=consumer,args=(q,))

 

    #開始

    p1.start()

    p2.start()

    p3.start()

    c1.start()

 

    p1.join() #必須保證生產者全部生產完畢,才應該發送結束信號

    p2.join()

    p3.join()

    q.put(None) #有幾個消費者就應該發送幾次結束信號None

    q.put(None) #發送結束信號

    print('主')

   

# JoinableQueue([maxsize]) :創建可連接的共享進程隊列。

像是一個Queue對象,但隊列允許項目的使用者通知生產者項目已經被成功處理。

通知進程是使用共享的信號和條件變量來實現的。

 

# 方法介紹:JoinableQueue的實例p除了與Queue對象相同的方法之外,還具有

q.task_done()

    使用者使用此方法發出信號,表示q.get()返回的項目已經被處理。

    如果調用此方法的次數大於從隊列中刪除的項目數量,將引發ValueError異常。

q.join()

    生產者將使用此方法進行阻塞,直到隊列中所有項目均被處理。

    阻塞將持續到爲隊列中的每個項目均調用q.task_done()方法爲止。

    下面的例子說明如何建立永遠運行的進程,使用和處理隊列上的項目。

    生產者將項目放入隊列,並等待它們被處理。

 

# JoinableQueue隊列實現消費之生產者模型

from multiprocessing import Process, JoinableQueue

import time, random, os

def consumer(q):

    while True:

        res=q.get()

        time.sleep(random.randint(1, 3))

        print('\033[45m%s 喫 %s\033[0m' % (os.getpid(), res))

        # 向q.join()發送一次信號,證明一個數據已經被取走了

        q.task_done()

 

def producer(name,q):

    for i in range(10):

        time.sleep(random.randint(1,3))

        res='%s%s' %(name,i)

        q.put(res)

        print('\033[44m%s 生產了 %s\033[0m' %(os.getpid(),res))

    # 生產完畢,使用此方法進行阻塞,直到隊列中所有項目均被處理。

    q.join()

 

 

if __name__ == '__main__':

    q=JoinableQueue()

    # 生產者們:即廚師們

    p1=Process(target=producer,args=('包子',q))

    p2=Process(target=producer,args=('骨頭',q))

    p3=Process(target=producer,args=('泔水',q))

 

    # 消費者們:即喫貨們

    c1=Process(target=consumer,args=(q,))

    c2=Process(target=consumer,args=(q,))

    c1.daemon=True

    c2.daemon=True

 

    # 開始

    p_l=[p1,p2,p3,c1,c2]

    for p in p_l:

        p.start()

 

    p1.join()

    p2.join()

    p3.join()

    print('主')

   

    # 主進程等--->p1,p2,p3等---->c1,c2

    # p1,p2,p3結束了,證明c1,c2肯定全都收完了p1,p2,p3發到隊列的數據

    # 因而c1,c2也沒有存在的價值了,不需要繼續阻塞在進程中影響主進程了。

    #      應該隨着主進程的結束而結束,所以設置成守護進程就可以了。

   

"""

 

"""

# 進程之間的數據共享

基於消息傳遞的併發編程是大勢所趨,

但進程間應該儘量避免通信,即便需要通信,也應該選擇進程安全的工具來避免加鎖帶來的問題。

以後我們會嘗試使用數據庫來解決現在進程之間的數據共享問題。

 

Manager模塊介紹

    進程間數據是獨立的,可以藉助於隊列或管道實現通信,二者都是基於消息傳遞的.

    雖然進程間數據獨立,但可以通過Manager實現數據共享,事實上Manager的功能遠不止於此.

 

Manager例子

from multiprocessing import Manager,Process,Lock

def work(d,lock):

    with lock: #不加鎖而操作共享的數據,肯定會出現數據錯亂

        d['count']-=1

 

if __name__ == '__main__':

    lock=Lock()

    with Manager() as m:

        dic=m.dict({'count':100})

        p_l=[]

        for i in range(100):

            p=Process(target=work,args=(dic,lock))

            p_l.append(p)

            p.start()

        for p in p_l:

            p.join()

        print(dic

       

"""

 

"""

進程池和multiprocess.Pool模塊

定義一個池子,在裏面放上固定數量的進程,有需求來了,就拿一個池中的進程來處理任務,

等到處理完畢,進程並不關閉,而是將進程再放回進程池中繼續等待任務。有很多任務需要執行,

池中的進程數量不夠,任務就要等待之前的進程執行任務完畢歸來,拿到空閒進程才能繼續執行.

 

Pool([numprocess  [,initializer [, initargs]]]):創建進程池

# 參數介紹:

1 numprocess:要創建的進程數,如果省略,將默認使用cpu_count()的值

2 initializer:是每個工作進程啓動時要執行的可調用對象,默認爲None

3 initargs:是要傳給initializer的參數組

 

# 主要方法

p.apply(func [, args [, kwargs]]):

    在一個池工作進程中執行func(*args,**kwargs),然後返回結果。

    需要強調的是:此操作並不會在所有池工作進程中並執行func函數。

    如果要通過不同參數併發地執行func函數,

    必須從不同線程調用p.apply()函數或者使用p.apply_async()

 

p.apply_async(func [, args [, kwargs]]):

    在一個池工作進程中執行func(*args,**kwargs),然後返回結果。

    此方法的結果是AsyncResult類的實例,callback是可調用對象,接收輸入參數。

    當func的結果變爲可用時,將理解傳遞給callback。callback禁止執行任何阻塞操作,

    否則將接收其他異步操作中的結果。

  

p.close():

    關閉進程池,防止進一步操作。如果所有操作持續掛起,它們將在工作進程終止前完成

 

P.jion():

    等待所有工作進程退出。此方法只能在close()或teminate()之後調用

 

# 其他方法(瞭解)

方法apply_async()和map_async()的返回值是AsyncResul的實例obj。實例具有以下方法

obj.get():返回結果,如果有必要則等待結果到達,

    timeout是可選的。如果在指定時間內還沒有到達,將引發一場。

    如果遠程操作中引發了異常,它將在調用此方法時再次被引發。

obj.ready():

    如果調用完成,返回True

obj.successful():

    如果調用完成且沒有引發異常,返回True,

    如果在結果就緒之前調用此方法,引發異常

obj.wait([timeout]):

    等待結果變爲可用。

obj.terminate():

    立即終止所有工作進程,同時不執行任何清理或結束任何掛起工作。

    如果p被垃圾回收,將自動調用此函數

 

# p.map進程池和進程效率測試--進程池和多進程效率對比

 

 

# 進程池的同步調用

import os,time

from multiprocessing import Pool

 

def work(n):

    print('%s run' % os.getpid())

    time.sleep(3)

    return n**2

 

if __name__ == '__main__':

    # 進程池中從無到有創建三個進程,以後一直是這三個進程在執行任務

    p=Pool(3)

    res_l=[]

    for i in range(10):

        # 同步調用,直到本次任務執行完畢拿到res,

        # 等待任務work執行的過程中可能有阻塞也可能沒有阻塞

        # 但不管該任務是否存在阻塞,同步調用都會在原地等着

        res=p.apply(work,args=(i,))

                                   

    print(res_l)

   

 

# 進程池異步調用

import os

import time

import random

from multiprocessing import Pool

 

def work(n):

    print('%s run' % os.getpid())

    time.sleep(random.random())

    return n**2

 

if __name__ == '__main__':

    # 進程池中從無到有創建三個進程,以後一直是這三個進程在執行任務

    p=Pool(3)

    res_l=[]

    for i in range(10):

        # 異步運行,根據進程池中有的進程數,每次最多3個子進程在異步執行

        # 返回結果之後,將結果放入列表,歸還進程,之後再執行新的任務

        # 需要注意的是,進程池中的三個進程不會同時開啓或者同時結束

        # 而是執行完一個就釋放一個進程,這個進程就去接收新的任務。

        res=p.apply_async(work,args=(i,))

   

        res_l.append(res)

 

    # 異步apply_async用法:如果使用異步提交的任務,主進程需要使用jion,

    # 等待進程池內任務都處理完,然後可以用get收集結果

    # 否則,主進程結束,進程池可能還沒來得及執行,也就跟着一起結束了

    p.close()

    p.join()

    for res in res_l:

        # 使用get來獲取apply_aync的結果,如果是apply,則沒有get方法,

        # 因爲apply是同步執行,立刻獲取結果,也根本無需get

        print(res.get())

 

# 進程池版併發聊天

# server端:

# Pool內的進程數默認是cpu核數,假設爲4(查看方法os.cpu_count())

# 開啓6個客戶端,會發現2個客戶端處於等待狀態

# 在每個進程內查看pid,會發現pid使用爲4個,即多個客戶端公用4個進程

from socket import *

from multiprocessing import Pool

import os

 

server=socket(AF_INET, SOCK_STREAM)

server.setsockopt(SOL_SOCKET, SO_REUSEADDR,1)

server.bind(('127.0.0.1', 8080))

server.listen(5)

 

def talk(conn):

    print('進程pid: %s' %os.getpid())

    while True:

        try:

            msg=conn.recv(1024)

            if not msg:break

            conn.send(msg.upper())

        except Exception:

            break

 

if __name__ == '__main__':

    p=Pool(4)

    while True:

        conn,*_=server.accept()

        p.apply_async(talk,args=(conn,))

        # 同步的話,則同一時間只有一個客戶端能訪問

        # p.apply(talk,args=(conn,client_addr))

 

# client端:

from socket import *

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8080))

 

 

while True:

    msg=input('>>: ').strip()

    if not msg:continue

 

    client.send(msg.encode('utf-8'))

    msg=client.recv(1024)

    print(msg.decode('utf-8'))

 

 

# 使用多進程請求多個url來減少網絡等待浪費的時間

# 爬蟲實例

import re

from urllib.request import urlopen

from multiprocessing import Pool

 

def get_page(url,pattern):

    response=urlopen(url).read().decode('utf-8')

    return pattern,response

 

def parse_page(info):

    pattern,page_content=info

    res=re.findall(pattern,page_content)

    for item in res:

        dic={

            'index':item[0].strip(),

            'title':item[1].strip(),

            'actor':item[2].strip(),

            'time':item[3].strip(),

        }

        print(dic)

if __name__ == '__main__':

    regex = r'<dd>.*?<.*?class="board-index.*?>(\d+)</i>.*?title="(.*?)".*?class="movie-item-info".*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>'

    pattern1=re.compile(regex,re.S)

 

    url_dic={

        'http://maoyan.com/board/7':pattern1,

    }

 

    p=Pool()

    res_l=[]

    for url,pattern in url_dic.items():

        res=p.apply_async(get_page,args=(url,pattern),callback=parse_page)

        res_l.append(res)

 

    for i in res_l:

        i.get()

       

 

"""

 

#!/usr/bin/env python

# -*- coding:utf-8 -*-

"""

線程-- 能獨立運行的基本單位——線程

 

進程的缺點:

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

2、進程在執行的過程中如果阻塞,例如等待輸入,整個進程就會掛起,

    即使進程中有些工作不依賴於輸入的數據,也將無法執行。

 

 

進程是資源分配的最小單位,線程是CPU調度的最小單位.

每一個進程中至少有一個線程

 

進程和線程的區別:

1)地址空間和其它資源(如打開文件):進程間相互獨立,同一進程的各線程間共享。

    某進程內的線程在其它進程不可見。

2)通信:進程間通信IPC,線程間可以直接讀寫進程數據段(如全局變量)

    來進行通信——需要進程同步和互斥手段的輔助,以保證數據的一致性。

3)調度和切換:線程上下文切換比進程上下文切換要快得多。

4)在多線程操作系統中,進程不是一個可執行的實體。

 

關於進程線程協程的 操作系統可以歸結爲三點:

(1)以多進程形式,允許多個任務同時運行;

(2)以多線程形式,允許單個任務分成不同的部分運行;

(3)提供協調機制,一方面防止進程之間和線程之間產生衝突,

    另一方面允許進程之間和線程之間共享資源

 

每個線程都是作爲利用CPU的基本單位,是花費最小開銷的實體,

線程的屬性:

1)輕型實體;

    線程中的實體基本上不擁有系統資源,只是有一點必不可少的、能保證獨立運行的資源,

    線程的實體包括程序、數據和TCB。線程是動態概念,它的動態特性由線程控制塊TCB控制

2)獨立調度和分派的基本單位;

    多線程OS中,線程是能獨立運行的基本單位,因而也是獨立調度和分派的基本單位。

    由於線程很“輕”,故線程的切換非常迅速且開銷小(在同一進程中的)。

3)共享進程資源;

    線程在同一進程中的各個線程,都可以共享該進程所擁有的資源,這首先表現在:

    所有線程都具有相同的進程id,這意味着,線程可以訪問該進程的每一個內存資源;

    此外,還可以訪問進程所擁有的已打開文件、定時器、信號量機構等。

    由於同一個進程內的線程共享內存和文件,所以線程之間互相通信不必調用內核。

4)可併發執行;

    在一個進程中的多個線程之間,可以併發執行,甚至允許在一個進程中所有線程都能併發執行;

    同樣,不同進程中的線程也能併發執行,充分利用和發揮了處理機與外圍設備並行工作的能力。

 

註釋:

    TCB:

    (1)線程狀態。

    (2)當線程不運行時,被保存的現場資源。

    (3)一組執行堆棧。

    (4)存放每個線程的局部變量主存區。

    (5)訪問同一個進程中的主存和其它資源。

    用於指示被執行指令序列的程序計數器、保留局部變量、

    少數狀態參數和返回地址等的一組寄存器和堆棧

 

類似於進程,每個線程也有自己的堆棧,不同於進程,線程庫無法利用時鐘中斷強制線程讓出CPU,

可以調用thread_yield運行線程自動放棄cpu,讓另外一個線程運行。

 

線程通常是有益的,但是帶來了不小程序設計難度,線程的問題是:

1. 父進程有多個線程,那麼開啓的子線程是否需要同樣多的線程

2. 在同一個進程中,如果一個線程關閉了文件,而另外一個線程正準備往該文件內寫內容呢?

因此,在多線程的代碼中,需要更多的心思來設計程序的邏輯、保護程序的數據。

 

"""

 

"""

全局解釋器鎖GIL

Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制.

在多線程環境中,Python 虛擬機按以下方式執行:

    a、設置 GIL;

    b、切換到一個線程去運行;

    c、運行指定數量的字節碼指令或者線程主動讓出控制(可以調用 time.sleep(0));

    d、把線程設置爲睡眠狀態;

    e、解鎖 GIL;

    d、再次重複以上所有步驟。

Python提供用於多線程編程的模塊,包括thread、threading和Queue等。

    thread和threading模塊允許程序員創建和管理線程。 

     thread模塊提供了基本的線程和鎖的支持,

     threading提供了更高級別、功能更強的線程管理的功能。

     Queue模塊允許用戶創建一個可以用於多個線程之間共享數據的隊列數據結構。

避免使用thread模塊,因爲更高級別的threading模塊更爲先進,對線程的支持更爲完善,

  

"""

 

"""

threading模塊

multiprocess模塊的完全模仿了threading模塊的接口,

二者在使用層面,有很大的相似性.

# 創建線程1

from threading import Thread

import time

def sayhi(name):

    time.sleep(2)

    print('%s say hello' %name)

 

if __name__ == '__main__':

    t=Thread(target=sayhi, args=('egon',))

    t.start()

    print('主線程')

   

# 創建線程2

from threading import Thread

import time

class Sayhi(Thread):

    def __init__(self, name):

        super().__init__()

        self.name=name

    def run(self):

        time.sleep(2)

        print('%s say hello' % self.name)

 

 

if __name__ == '__main__':

    t = Sayhi('egon')

    t.start()

    print('主線程')

 

# 多線程與多進程

from threading import Thread

from multiprocessing import Process

import os

 

def work():

    print('hello',os.getpid())

 

if __name__ == '__main__':

    #part1:在主進程下開啓多個線程,每個線程都跟主進程的pid一樣

    t1=Thread(target=work)

    t2=Thread(target=work)

    t1.start()

    t2.start()

    print('主線程/主進程pid',os.getpid())

 

    #part2:開多個進程,每個進程都有不同的pid

    p1=Process(target=work)

    p2=Process(target=work)

    p1.start()

    p2.start()

    print('主線程/主進程pid',os.getpid())

 

# 開啓效率的比較

from threading import Thread

from multiprocessing import Process

import os

 

def work():

    print('hello')

 

if __name__ == '__main__':

    #在主進程下開啓線程

    t=Thread(target=work)

    t.start()

    print('主線程/主進程')

    '''

    打印結果:

    hello

    主線程/主進程

    '''

 

    #在主進程下開啓子進程

    t=Process(target=work)

    t.start()

    print('主線程/主進程')

    '''

    打印結果:

    主線程/主進程

    hello

    '''

 

# 內存數據的共享問題

from  threading import Thread

from multiprocessing import Process

import os

def work():

    global n

    n=0

 

if __name__ == '__main__':

    # n=100

    # p=Process(target=work)

    # p.start()

    # p.join()

    # # 毫無疑問子進程p已經將自己的全局的n改成了0,但改的僅僅是它自己的,

    # # 查看父進程的n仍然爲100

    # print('主',n)

 

 

    n=1

    t=Thread(target=work)

    t.start()

    t.join()

    # 查看結果爲0,因爲同一進程內的線程之間共享進程內的數據

    print('主', n)

同一進程內的線程共享該進程的數據?

 

"""

 

"""

# 多線程實現socket

server端:

import multiprocessing

import threading

 

import socket

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.bind(('127.0.0.1',8080))

s.listen(5)

 

def action(conn):

    while True:

        data=conn.recv(1024)

        print(data)

        conn.send(data.upper())

 

if __name__ == '__main__':

 

    while True:

        conn,addr=s.accept()

 

 

        p=threading.Thread(target=action,args=(conn,))

        p.start()

 

client端:

import socket

 

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect(('127.0.0.1',8080))

 

while True:

    msg=input('>>: ').strip()

    if not msg:continue

 

    s.send(msg.encode('utf-8'))

    data=s.recv(1024)

    print(data)

 

"""

 

"""

# Thread類的其他方法

Thread實例對象的方法

  # isAlive(): 返回線程是否活動的。

  # getName(): 返回線程名。

  # setName(): 設置線程名。

 

threading模塊提供的一些方法:

  # threading.currentThread(): 返回當前的線程變量。

  # threading.enumerate(): 返回一個包含正在運行的線程的list。

        正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。

  # threading.activeCount(): 返回正在運行的線程數量,

        與len(threading.enumerate())有相同的結果。

       

       

# 示例

from threading import Thread

import threading

from multiprocessing import Process

import os

 

def work():

    import time

    time.sleep(3)

    print(threading.current_thread().getName())

 

 

if __name__ == '__main__':

    #在主進程下開啓線程

    t=Thread(target=work)

    t.start()

 

    print(threading.current_thread().getName())

    print(threading.current_thread()) #主線程

    print(threading.enumerate()) #連同主線程在內有兩個運行的線程

    print(threading.active_count())

    print('主線程/主進程')

 

    '''

    打印結果:

    MainThread

    <_MainThread(MainThread, started 140735268892672)>

    [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]

    主線程/主進程

    Thread-1

    '''

 

"""

 

"""

# join方法

from threading import Thread

import time

def sayhi(name):

    time.sleep(2)

    print('%s say hello' %name)

 

if __name__ == '__main__':

    t=Thread(target=sayhi,args=('egon',))

    t.start()

    t.join()

    print('主線程')

    print(t.is_alive())

    '''

    egon say hello

    主線程

    False

    '''

"""

 

"""

# 守護線程

    無論是進程還是線程,都遵循:守護xx會等待主xx運行完畢後被銷燬。

    需要強調的是:運行完畢並非終止運行。

 

1.對主進程來說,運行完畢指的是主進程代碼運行完畢

2.對主線程來說,運行完畢指的是主線程所在的進程內所有非守護線程統統運行完畢,

    主線程纔算運行完畢

 

1.主進程在其代碼結束後就已經算運行完畢了(守護進程在此時就被回收),

然後主進程會一直等非守護的子進程都運行完畢後回收子進程的資源(否則會產生殭屍進程),纔會結束;

2.主線程在其他非守護線程運行完畢後纔算運行完畢(守護線程在此時就被回收)。

    因爲主線程的結束意味着進程的結束,進程整體的資源都將被回收,

    而進程必須保證非守護線程都運行完畢後才能結束。

   

# 守護線程1

from threading import Thread

import time

def sayhi(name):

    time.sleep(2)

    print('%s say hello' %name)

 

if __name__ == '__main__':

    t=Thread(target=sayhi,args=('egon',))

    t.setDaemon(True) #必須在t.start()之前設置

    t.start()

 

    print('主線程')

    print(t.is_alive())

    '''

    主線程

    True

    '''

 

# 守護線程2

from threading import Thread

import time

def foo():

    print(123)

    time.sleep(1)

    print("end123")

 

def bar():

    print(456)

    time.sleep(3)

    print("end456")

 

 

t1=Thread(target=foo)

t2=Thread(target=bar)

 

t1.daemon=True

t1.start()

t2.start()

print("main-------")

 

 

"""

 

"""

 

# 同步鎖

# 多個線程搶佔資源的情況

from threading import Thread

import os,time

def work():

    global n

    temp=n

    time.sleep(0.1)

    n=temp-1

if __name__ == '__main__':

    n=100

    l=[]

    for i in range(100):

        p=Thread(target=work)

        l.append(p)

        p.start()

    for p in l:

        p.join()

 

    print(n) #結果可能爲99

   

import threading

R=threading.Lock()

R.acquire()

'''

對公共數據的操作

'''

R.release()

 

# 同步鎖的引用

from threading import Thread,Lock

import os,time

def work():

    global n

    lock.acquire()

    temp=n

    time.sleep(0.1)

    n=temp-1

    lock.release()

if __name__ == '__main__':

    lock=Lock()

    n=100

    l=[]

    for i in range(100):

        p=Thread(target=work)

        l.append(p)

        p.start()

    for p in l:

        p.join()

   

    # 結果肯定爲0,由原來的併發執行變成串行,犧牲了執行效率保證了數據安全

    print(n)

   

# 互斥鎖與join的區別

 

 

# 死鎖與遞歸鎖

進程也有死鎖與遞歸鎖;

所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程中,

因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。

此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

例如:

from threading import Lock as Lock

import time

mutexA=Lock()

mutexA.acquire()

mutexA.acquire()

print(123)

mutexA.release()

mutexA.release()

 

解決方法:遞歸鎖,python爲支持在同一線程中多次請求同一資源,python提供了可重入鎖RLock。

RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,

從而使得資源可以被多次require。直到一個線程所有的acquire都被release,

其他的線程才能獲得資源。

例:

from threading import RLock as Lock

import time

mutexA=Lock()

mutexA.acquire()

mutexA.acquire()

print(123)

mutexA.release()

mutexA.release()

 

# 科學家吃麪 死鎖問題

import time

from threading import Thread,Lock

noodle_lock = Lock()

fork_lock = Lock()

def eat1(name):

    noodle_lock.acquire()

    print('%s 搶到了麪條'%name)

    fork_lock.acquire()

    print('%s 搶到了叉子'%name)

    print('%s 吃麪'%name)

    fork_lock.release()

    noodle_lock.release()

 

def eat2(name):

    fork_lock.acquire()

    print('%s 搶到了叉子' % name)

    time.sleep(1)

    noodle_lock.acquire()

    print('%s 搶到了麪條' % name)

    print('%s 吃麪' % name)

    noodle_lock.release()

    fork_lock.release()

 

for name in ['哪吒','egon','yuan']:

    t1 = Thread(target=eat1,args=(name,))

    t2 = Thread(target=eat2,args=(name,))

    t1.start()

    t2.start()

   

# 遞歸鎖解決死鎖問題

import time

from threading import Thread,RLock

fork_lock = noodle_lock = RLock()

def eat1(name):

    noodle_lock.acquire()

    print('%s 搶到了麪條'%name)

    fork_lock.acquire()

    print('%s 搶到了叉子'%name)

    print('%s 吃麪'%name)

    fork_lock.release()

    noodle_lock.release()

 

def eat2(name):

    fork_lock.acquire()

    print('%s 搶到了叉子' % name)

    time.sleep(1)

    noodle_lock.acquire()

    print('%s 搶到了麪條' % name)

    print('%s 吃麪' % name)

    noodle_lock.release()

    fork_lock.release()

 

for name in ['哪吒','egon','yuan']:

    t1 = Thread(target=eat1,args=(name,))

    t2 = Thread(target=eat2,args=(name,))

    t1.start()

    t2.start()

 

#  只用遞歸鎖不用引起死鎖  怎樣?

 

 

"""

 

"""

# 線程隊列

queue隊列 :使用import queue,用法與進程Queue一樣

# 先進先出class queue.Queue(maxsize=0) #先進先出

import queue

 

q=queue.Queue()

q.put('first')

q.put('second')

q.put('third')

 

print(q.get())

print(q.get())

print(q.get())

'''

結果(先進先出):

first

second

third

'''

# 後進先出class queue.LifoQueue(maxsize=0) #last in fisrt out

import queue

 

q=queue.LifoQueue()

q.put('first')

q.put('second')

q.put('third')

 

print(q.get())

print(q.get())

print(q.get())

'''

結果(後進先出):

third

second

first

'''

# 存儲數據時可設置優先級的隊列class queue.PriorityQueue(maxsize=0)

import queue

 

q=queue.PriorityQueue()

#put進入一個元組,元組的第一個元素是優先級(通常是數字,也可以是非數字之間的比較),數字越小優先級越高

q.put((20,'a'))

q.put((10,'b'))

q.put((30,'c'))

 

print(q.get())

print(q.get())

print(q.get())

'''

結果(數字越小優先級越高,優先級高的優先出隊):

(10, 'b')

(20, 'a')

(30, 'c')

'''

 

"""

 

"""

Python標準模塊--concurrent.futures 啓動並行任務

concurrent.futures模塊提供了高度封裝的異步調用接口

ThreadPoolExecutor:線程池,提供異步調用

ProcessPoolExecutor: 進程池,提供異步調用

#2 基本方法

#submit(fn, *args, **kwargs)

異步提交任務

 

#map(func, *iterables, timeout=None, chunksize=1)

取代for循環submit的操作

 

#shutdown(wait=True)

相當於進程池的pool.close()+pool.join()操作

wait=True,等待池內所有任務執行完畢回收完資源後才繼續

wait=False,立即返回,並不會等待池內的任務執行完畢

但不管wait參數爲何值,整個程序都會等到所有任務執行完畢

submit和map必須在shutdown之前

 

#result(timeout=None)

取得結果

 

#add_done_callback(fn)

回調函數

 

# done()

判斷某一個線程是否完成

 

# cancle()

取消某個任務

 

# ProcessPoolExecutor

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

 

import os,time,random

def task(n):

    print('%s is runing' %os.getpid())

    time.sleep(random.randint(1,3))

    return n**2

 

if __name__ == '__main__':

 

    executor=ProcessPoolExecutor(max_workers=3)

 

    futures=[]

    for i in range(11):

        future=executor.submit(task,i)

        futures.append(future)

    executor.shutdown(True)

    print('+++>')

    for future in futures:

        print(future.result())

 

 

# ThreadPoolExecutor

 

 

# map 的用法

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

 

import os,time,random

def task(n):

    print('%s is runing' %os.getpid())

    time.sleep(random.randint(1,3))

    return n**2

 

if __name__ == '__main__':

 

    executor=ThreadPoolExecutor(max_workers=3)

 

    # for i in range(11):

    #     future=executor.submit(task,i)

 

    executor.map(task,range(1,12)) #map取代了for+submit

   

 

# 回調函數

 

"""

#!/usr/bin/env python

# -*- coding:utf-8 -*-

"""

1、yiled可以保存狀態,yield的狀態保存與操作系統的保存線程狀態很像,

    但是yield是代碼級別控制的,更輕量級。

2、send可以把一個函數的結果傳給另外一個函數,以此實現單線程內程序之間的切換

 

# 單純的切換反而會降低效率

#串行執行

import time

def consumer(res):

    '''任務1:接收數據,處理數據'''

    pass

 

def producer():

    '''任務2:生產數據'''

    res=[]

    for i in range(10000000):

        res.append(i)

    return res

 

start=time.time()

#串行執行

res=producer()

consumer(res) #寫成consumer(producer())會降低執行效率

stop=time.time()

print(stop-start) #1.5536692142486572

 

 

 

#基於yield併發執行

import time

def consumer():

    '''任務1:接收數據,處理數據'''

    while True:

        x=yield

 

def producer():

    '''任務2:生產數據'''

    g=consumer()

    next(g)

    for i in range(10000000):

        g.send(i)

 

start=time.time()

#基於yield保存狀態,實現兩個任務直接來回切換,即併發的效果

#PS:如果每個任務中都加上打印,那麼明顯地看到兩個任務的打印是你一次我一次,即併發執行的.

producer()

 

stop=time.time()

print(stop-start) #2.0272178649902344

 

# yield無法做到遇到io阻塞

import time

def consumer():

    '''任務1:接收數據,處理數據'''

    while True:

        x=yield

 

def producer():

    '''任務2:生產數據'''

    g=consumer()

    next(g)

    for i in range(10000000):

        g.send(i)

        time.sleep(2)

 

start=time.time()

# 併發執行,但是任務producer遇到io就會阻塞住,並不會切到該線程內的其他任務去執行

producer()

 

stop=time.time()

print(stop-start)

 

協程的本質就是在單線程下,由用戶自己控制一個任務遇到io阻塞了就切換另外一個任務去執行,

    以此來提升效率。

1、 可以控制多個任務之間的切換,切換之前將任務的狀態保存下來,

    以便重新運行時,可以基於暫停的位置繼續執行。

2、作爲1的補充:可以檢測io操作,在遇到io操作的情況下才發生切換

 

協程:是單線程下的併發,又稱微線程;用戶在單線程內控制協程的切換

協程是一種用戶態的輕量級線程,即協程是由用戶程序自己控制調度的。

 

1. python的線程屬於內核級別的,即由操作系統控制調度

(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其他線程運行。

2. 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操作系統)控制切換,

    以此來提升效率(!!!非io操作的切換與效率無關)。

 

協程的優點:

1. 協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因而更加輕量級。

2. 單線程內就可以實現併發的效果,最大限度地利用cpu

 

協程缺點:

1. 協程的本質是單線程下,無法利用多核,

    可以是一個程序開啓多個進程,每個進程內開啓多個線程,每個線程內開啓協程。

2. 協程指的是單個線程,因而一旦協程出現阻塞,將會阻塞整個線程

 

總結協程的特點:

1、必須在只有一個單線程裏實現併發

2、修改共享數據不需加鎖

3、用戶程序裏自己保存多個控制流的上下文棧

4、附加:一個協程遇到IO操作自動切換到其它協程

    (如何實現檢測IO,yield、greenlet都無法實現,就用到了gevent模塊(select機制))

 

"""

 

"""

Greenlet模塊 安裝 pip3 install greenlet

# greenlet實現狀態切換

from greenlet import greenlet

 

def eat(name):

    print('%s eat 1' %name)

    g2.switch('egon')

    print('%s eat 2' %name)

    g2.switch()

def play(name):

    print('%s play 1' %name)

    g1.switch()

    print('%s play 2' %name)

 

g1=greenlet(eat)

g2=greenlet(play)

 

# 可以在第一次switch時傳入參數,以後都不需要

g1.switch('egon')

 

 

# 單純的切換(在沒有io的情況下或者沒有重複開闢內存空間的操作),反而會降低程序的執行速度

#順序執行

import time

def f1():

    res=1

    for i in range(100000000):

        res+=i

 

def f2():

    res=1

    for i in range(100000000):

        res*=i

 

start=time.time()

f1()

f2()

stop=time.time()

print('run time is %s' %(stop-start)) #10.985628366470337

 

#切換

from greenlet import greenlet

import time

def f1():

    res=1

    for i in range(100000000):

        res+=i

        g2.switch()

 

def f2():

    res=1

    for i in range(100000000):

        res*=i

        g1.switch()

 

start=time.time()

g1=greenlet(f1)

g2=greenlet(f2)

g1.switch()

stop=time.time()

print('run time is %s' %(stop-start)) # 52.763017892837524

 

greenlet只是提供了一種比generator更加便捷的切換方式,當切到一個任務執行時如果遇到io,

那就原地阻塞,仍然是沒有解決遇到IO自動切換來提升效率的問題

 

"""

 

"""

單線程裏的這20個任務的代碼通常會既有計算操作又有阻塞操作,

我們完全可以在執行任務1時遇到阻塞,就利用阻塞的時間去執行任務2。。。。如此,才能提高效率

 

Gevent模塊 安裝 pip install gevent

Gevent 是一個第三方庫,可以輕鬆通過gevent實現併發同步或異步編程,

gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程,

Greenlet全部運行在主程序操作系統進程的內部,但它們被協作式地調度.

 

# 用法介紹

g1=gevent.spawn(func,1,,2,3,x=4,y=5)創建一個協程對象g1,

    spawn括號內第一個參數是函數名,如eat,後面可以有多個參數,

    可以是位置實參或關鍵字實參,都是傳給函數eat的

 

g2=gevent.spawn(func2)

 

g1.join() #等待g1結束

 

g2.join() #等待g2結束

 

#或者上述兩步合作一步:gevent.joinall([g1,g2])

 

g1.value#拿到func1的返回值

 

# 遇到io主動切換

import gevent

def eat(name):

    print('%s eat 1' %name)

    gevent.sleep(2)

    print('%s eat 2' %name)

 

def play(name):

    print('%s play 1' %name)

    gevent.sleep(1)

    print('%s play 2' %name)

 

 

g1=gevent.spawn(eat,'egon')

g2=gevent.spawn(play,name='egon')

g1.join()

g2.join()

#或者gevent.joinall([g1,g2])

print('主')

 

# 要用gevent,需要將from gevent import monkey;monkey.patch_all()放到文件的開頭

from gevent import monkey;monkey.patch_all()

 

import gevent

import time

def eat():

    print('eat food 1')

    time.sleep(2)

    print('eat food 2')

 

def play():

    print('play 1')

    time.sleep(1)

    print('play 2')

 

g1=gevent.spawn(eat)

g2=gevent.spawn(play)

gevent.joinall([g1,g2])

print('主')

 

我們可以用threading.current_thread().getName()來查看每個g1和g2,

查看的結果爲DummyThread-n,即假線程

from gevent import monkey;monkey.patch_all()

import threading

import gevent

import time

def eat():

    print(threading.current_thread().getName())

    print('eat food 1')

    time.sleep(2)

    print('eat food 2')

 

def play():

    print(threading.current_thread().getName())

    print('play 1')

    time.sleep(1)

    print('play 2')

 

g1=gevent.spawn(eat)

g2=gevent.spawn(play)

gevent.joinall([g1,g2])

print('主')

 

# Gevent之同步與異步

 

from gevent import spawn,joinall,monkey;monkey.patch_all()

 

import time

def task(pid):

 

    # Some non-deterministic task

 

    time.sleep(0.5)

    print('Task %s done' % pid)

 

def synchronous():  # 同步

    for i in range(10):

        task(i)

 

def asynchronous(): # 異步

    g_l=[spawn(task,i) for i in range(10)]

    joinall(g_l)

    print('DONE')

   

if __name__ == '__main__':

    print('Synchronous:')

    synchronous()

    print('Asynchronous:')

    asynchronous()

 

#  上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。

#  初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數,

#  後者阻塞當前流程,並執行所有給定的greenlet任務。

    執行流程只會在 所有greenlet執行完後纔會繼續向下走.

 

"""

 

"""

# Gevent之應用舉例

    from gevent import monkey;monkey.patch_all()一定要放到導入socket模塊之前,

    否則gevent無法識別socket的阻塞

通過gevent實現單線程下的socket併發

server端:

from gevent import monkey;monkey.patch_all()

from socket import *

import gevent

 

#如果不想用money.patch_all()打補丁,可以用gevent自帶的socket

# from gevent import socket

# s=socket.socket()

 

def server(server_ip,port):

    s=socket(AF_INET,SOCK_STREAM)

    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

    s.bind((server_ip,port))

    s.listen(5)

    while True:

        conn,addr=s.accept()

        gevent.spawn(talk,conn,addr)

 

def talk(conn,addr):

    try:

        while True:

            res=conn.recv(1024)

            print('client %s:%s msg: %s' %(addr[0],addr[1],res))

            conn.send(res.upper())

    except Exception as e:

        print(e)

    finally:

        conn.close()

 

if __name__ == '__main__':

    server('127.0.0.1',8080)

 

client端:

from socket import *

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8080))

 

 

while True:

    msg=input('>>: ').strip()

    if not msg:continue

 

    client.send(msg.encode('utf-8'))

    msg=client.recv(1024)

    print(msg.decode('utf-8'))

 

# 多線程併發多個客戶端

from threading import Thread

from socket import *

import threading

 

def client(server_ip,port):

    #套接字對象一定要加到函數內,即局部名稱空間內,

    # 放在函數外則被所有線程共享,則大家公用一個套接字對象,那麼客戶端端口永遠一樣了

    c=socket(AF_INET,SOCK_STREAM)

    c.connect((server_ip,port))

 

    count=0

    while True:

        c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))

        msg=c.recv(1024)

        print(msg.decode('utf-8'))

        count+=1

if __name__ == '__main__':

    for i in range(500):

        t=Thread(target=client,args=('127.0.0.1',8080))

        t.start()

"""

 

願有更多的朋友,在網頁筆記結構上分享更邏輯和易讀的形式:

鏈接:暫無
提取碼:暫無

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