進程 線程-------------來自神祕大佬羊羊羊的指導

進程

多任務

程序的運行都是cpu和內存協同工作的結果
常用的操作系統都支持“多任務”的操作
問題1:什麼時多任務
  操作系統上同時運行多個任務,除了手動打開應用程序之外,還有很多的後臺程序
問題2:單核cpu是怎麼執行多任務
  操作系統會讓多個任務輪流交替執行,由於不同的任務之間的切換速度很快,導致肉眼無法識別,
  多核cpu實現多任務:真正的並行執行之惡能在多核cpu上執行,但是現實情況下,任務的數量可能會遠遠大於cpu的核心數量,操作系統會將每個任務調度,讓每個cpu核心上輪流執行
  並行:真正一起執行,任務的數量小於cpu的核心數量[理想型]
  併發:看上去一起執行,任務數量大於cpu的核心數量[現實型]
問題3:什麼是進程?
  對於操作系統而言,一個任務就是一個進程[Process]
  例如:打開一個瀏覽器啓動了一個瀏覽器進程,打開一個Word啓動了一個word進程,但是,一個進程可以同時幹多件事情,在一個進程的內部,要同時幹多個事情,就需要同時執行多個子任務,將一個進程中的子任務被稱爲線程[Thread]

進程本質

是對一個程序的運行狀態和佔用資源[cpu,內存]的描述
進程是程序的一個運動過程,他指的是從代碼到加載到最後代碼加載完畢的整個過程
進程的特點:
  a.獨立性:不同的進程之間是相互獨立的,在操作系統中互不影響
  b.動態性:進程一旦被啓動之後,在操作系統中並不是靜止不動的,一直處於動態狀態
  c.併發性:在操作系統上交替執行

python多任務實現方式

a.多進程模式:啓動多個進程,每個進程雖然只有一個線程,通過該線程執行一個任務
  b.多線程模式:啓動一個進程,該進程啓動多個線程,每個線程執行一個任務
  c.協程模式:yield[函數生成器]
  d:多進程模式+多線程模式:啓動多個進程,每個進程啓動多個線程,執行的任務的數量會更多

使用

單任務現象

import  time

#單任務:一個函數執行了某個特定的功能
def run():
    while True:
        print("hello")
        time.sleep(0.5)

if __name__ == "__main__":
    while True:
        print("main")
        time.sleep(1)

    run()

#說明:單任務現象代碼從上往下依次執行,前面的代碼執行完畢,後面的代碼纔有執行的機會

啓動多進程實現多任務


import  time,os
#如果要實現多任務,Python中提供了相應的模塊
#每通過Process創建一個對象,則表示創建一個進程
from  multiprocessing import  Process

#子進程中的任務
def run(s):
    #os.getpid()獲取當前正在執行的進程id
    #os.getppid(獲取當前正在執行進程的父進程id
    print("子進程啓動:%s-%s" % (os.getpid(),os.getppid()))
    print(s)
    while True:
        print("hello")
        time.sleep(1)

if __name__ == "__main__":
    #1.__main__中執行的代碼表示主進程,也稱爲父進程
    #os.getpid獲取當前正在執行的進程id
    print("主進程啓動:%s" % (os.getpid()))

    #2.在主進程中創建子進程
    #Process(target,args),target表示子進程需要執行的任務【函數】,args表示需要執行的任務的參數,類型爲元組
    #2.1任務函數沒有參數
    #p1 = Process(target=run)

    #2.2任務函數有參數
    #注意:給子進程中的任務傳參的時候,函數有幾個參數,元組中的元素需要保持一致
    p1 = Process(target=run,args=("text",))

    #如果想要啓動一個子進程,則必須調用start
    p1.start()

    #主進程中的任務
    while True:
        print("main")
        time.sleep(1)

    run()

父子進程的執行順序

from  multiprocessing import  Process
import  time,os

def run():
    print("子進程啓動~~~~~")
    time.sleep(2)
    print("子進程結束~~~~~")


if __name__ == "__main__":
    print("父進程啓動")
    time.sleep(2)

    p = Process(target=run)

    p.start()

    p.join()

    print("父進程結束")

"""
默認情況下,都是主進程結束之後子進程才啓動
使用join之後,主進程會等待子進程結束之後才結束

注意:join的調用只能在start之後
"""

多個進程中的全局變量

from  multiprocessing import  Process
from time import  sleep
import  os

#需求:在一個進程中修改全局變量的值,觀察另一個進程中全局變量的值的變化

#全局變量
num = 100

def run():
    print("子進程開始:%s" % (os.getpid()))
    global  num
    num += 1
    print("子進程結束:num=%d" % (num))

if __name__ == "__main__":
    print("父進程開始:%s" % (os.getpid()))

    p = Process(target=run)
    p.start()

    p.join()

    print("父進程結束:num=%d" % (num))

"""
說明:在子進程中修改全局變量對父進程中的全局變量沒有影響
工作原理;在創建子進程的時候,實際上對全局變量做了一個備份,父進程中的num和子進程中的num是兩個不同的變量
進程是系統中程序執行和系統資源分配的基本單位,每個進程都有自己的數據段,代碼段,堆棧段
【進程之間是相互獨立的,資源不共享】
"""

驗證多個進程之間的獨立性

啓動大量子進程

如果要大量創建子進程,可以使用進程池的方式批量創建子進程

#進程池
from  multiprocessing import  Pool
import  os,time,random

#需求:創建多個子進程,讓子進程執行同一個任務,根據傳入的參數作爲進程的名稱進行區分

def run(name):
    print("子進程%s啓動:%s" % (name,os.getpid()))

    #通過time計算耗時的時間差
    start = time.time()
    time.sleep(random.choice(range(1,4)))
    end = time.time()

    print("子進程%s結束,進程號:%s,耗時:%s" % (name, os.getpid(),end - start))

if __name__ == "__main__":
    print("父進程啓動")

    #創建多個進程,採用進程池的方式,通過系統類Pool創建多個進程對象
    #Pool(num),num表示需要同時執行的任務的數量,可以省略,默認爲cpu的核心數量
    p = Pool(8)

    #通過循環的方式創建多個子進程
    for i in range(5):
        #創建子進程,放到進程池統一管理
        #apply_async(target,args)
        p.apply_async(run,args=(i,))

    #必須在join之前調用close,調用close之後將不能再添加新的進程
    p.close()

    #進程池對象調用join,觸發進程的啓動,父進程會等待所有的子進程執行結束之後才結束
    p.join()

    print("父進程結束")

"""
如果要使用進程池創建多個進程,必須調用close和join
"""
#需求:實現文件內容的拷貝

import  os,time

#實現文件的拷貝
def copyfile(rpath,wpath):
    fr = open(rpath,"rb")
    fw = open(wpath,"wb")

    content = fr.read()
    fw.write(content)

    fr.close()
    fw.close()

if __name__ == "__main__":
    path = r"C:\Users\Administrator\Desktop\text1"
    topath = r"C:\Users\Administrator\Desktop\text2"

    #獲取path下面的所有的內容
    filelist = os.listdir(path)

    start = time.time()

    for filename in filelist:
        copyfile(os.path.join(path,filename),os.path.join(topath,filename))

    end = time.time()

    print("總耗時:%.2f" % (end - start))
# 需求:實現文件內容的拷貝

import os, time
from  multiprocessing import  Pool


# 實現文件的拷貝
def copyfile(rpath, wpath):
    fr = open(rpath, "rb")
    fw = open(wpath, "wb")

    content = fr.read()
    fw.write(content)

    fr.close()
    fw.close()


if __name__ == "__main__":
    path = r"C:\Users\Administrator\Desktop\text1"
    topath = r"C:\Users\Administrator\Desktop\text3"

    # 獲取path下面的所有的內容
    filelist = os.listdir(path)

    p = Pool(4)

    start = time.time()

    for filename in filelist:
        #copyfile(os.path.join(path, filename), os.path.join(topath, filename))
        p.apply_async(copyfile,args=(os.path.join(path, filename),os.path.join(topath, filename)))

    p.close()
    p.join()

    end = time.time()

    print("總耗時:%.2f" % (end - start))

封裝進程對象

from  multiprocessing import  Process

import  os,time

#1.自定義一個類,繼承自Process
class CustomProcess(Process):
    #2.書寫構造函數,定義實例屬性,表示當前進程的名稱
    def __init__(self,name):
        #3.調用服了你的構造函數
        Process.__init__(self)
        self.name = name

    #4.定義成員函數,任務的執行函數
    def run(self):
        print("子進程(%s-%s)啓動" % (self.name,os.getpid()))
        #子進程的功能
        time.sleep(2)
        print("子進程(%s-%s)結束" % (self.name, os.getpid()))

if __name__ == "__main__":
     print("父進程啓動")

     p = CustomProcess("1111")
     p.start()

     p.join()

     print("父進程結束")

進程之間的通信

Process之間是需要相互通信的,操作系統提供了很多的辦法實現進程間的通信,Python中的multiprocessing包裝了底層的機制,提供了Queue[隊列],

#需求:在主進程中創建兩個子進程,一個進程用來向隊列中寫數據,另外一個進程用來從隊列中讀數據

from  multiprocessing import  Process,Queue
import  os,time,random

#1.寫數據的任務
def write(queue):
    print("進程%s開始" % (os.getpid()))
    print("開始寫入")

    for value in ["A","B","C"]:
        print("add %s to queue" % (value))

        #向隊列中添加數據
        queue.put(value)

        #稍休息片刻,接着加
        time.sleep(random.random())

    print("進程%s結束" % (os.getpid()))

#2.讀數據的任務
def read(queue):
    print("進程%s開始" % (os.getpid()))
    print("開始讀取")

    n = 0
    while n < 3:
        #從隊列中獲取數據
        value = queue.get(True)
        print("get %s from queue" % (value))
        n += 1

    print("進程%s結束" % (os.getpid()))

#3.在主進程中分別創建子進程,執行相應的任務
if __name__ == "__main__":
    print("父進程啓動")

    #3.1創建隊列對象,並傳參給每個進程
    q = Queue()

    #3.2創建進程對象,
    pr = Process(target=read,args=(q,))
    pw = Process(target=write,args=(q,))

    #3.3啓動子進程,讓進行數據的讀寫
    pw.start()
    pr.start()

    #3.4設置,讓所有子進程結束之後,才結束父進程
    pr.join()
    pw.join()

    print("父進程結束")

二.線程

概念

是進程的組成部分,一個進程可以有多個線程,每個線程去處理一個特定的子任務
注意:一個進程至少需要一個線程,否則該進程沒有意義.
線程的執行是搶佔式的,多個線程在同一個進程中可以併發執行,其實就是在cpu之間進行快速的切換,[每個線程都有爭搶時間片的機會,誰搶到時間片,則執行對應的線程,剩下的線程都會被掛起]

例如: 打開網易雲音樂----->啓動一個進程
   播放歌曲和刷新歌詞------->啓動了兩個線程
進程和線程的關係
a.一個程序啓動之後至少有一個進程
b.一個進程可以包含多個線程,但是至少需要一個線程,否則該進程沒有意義
c.進程之間的資源不共享,線程之間的資源是共享的
d.系統創建進程需要爲該進程重新分配系統資源,二創建線程容易的多,因此使用多線程實現多任務比多進程實現效率更高

創建線程

_thread模塊,提供了低級別的,原始的線程[低級別並不是不好,知識功能有限,底層採用的c語言]
threading模塊:高級模塊,對_thread進行了封裝,提供了_thread沒有的功能

import  threading,time


#2.定義子線程需要執行的任務
def run(num):
    print("子線程%s啓動" % (threading.current_thread().name))

    time.sleep(2)
    print("num=%d" % (num))

    print("子線程%s結束" % (threading.current_thread().name))

if __name__ == "__main__":
    #1.任何進程默認會啓動一個線程,稱爲主線程,在主線程中啓動其他的子線程
    #threading.current_thread()返回一個當前正在執行的線程對象
    #threading.current_thread().name獲取當前正在執行的線程的名稱,主線程默認的名稱爲MainThread
    print("主線程%s啓動" % (threading.current_thread().name))

    #3.創建子線程
    #threading.Thread(target,name,args),target需要執行的任務,name表示線程的名稱,args執行的任務函數的參數
    #3.1任務函數沒有參數
    #注意:name也可以省略,每個子線程默認有名稱,根據線程被創建的順序,爲Thread-1,Thread-2.....
    #t1 = threading.Thread(target=run,name="11111")

    #3.2任務函數有參數
    t1 = threading.Thread(target=run,args=(10,))

    #4.手動啓動子線程
    t1.start()

    #5.想讓主線程等待子線程執行結束之後才結束,設置join
    t1.join()

    print("主線程%s結束" % (threading.current_thread().name))

線程中的數據共享

問題:在多線程中,全局變量被所有線程所共享,所以,任何一個全局變量可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時修改同一個變量,容易將數據改亂

#需求:使用一個全局變量代表銀行卡餘額,兩個線程都可以訪問銀行卡的餘額

from  threading import  Thread

balance = 0

def change(n):
    global balance
    balance = balance + n
    balance = balance - n

#子線程的任務
def run(n):
    for _ in range(1000000):
        change(n)

if __name__ == "__main__":

    t1 = Thread(target=run,args=(5,))
    t2 = Thread(target=run,args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    #理論上,balance最終的結果爲0
    print(balance)

"""
問題:
    先加後減,理論上結果爲0,但是,如果訪問的次數變多,則結果不一定爲0

原因:
    使用多線程容易出現的臨界資源問題
    高級語言中一條語句分步執行的,所以balance = balance + n在內存中執行的順序是:
        a.計算balance + n,將結果存儲到一個臨時變量中   x = balance + n
        b.將臨時變量的值賦值給balance   balance = x

    1.正常情況下
        t1:x = balance + n  #5
        t1:balance = x   #balance = 5
        t1:x = balance - n   #0
        t1:balance = x    #0

        t2:x = balance + n  #8
        t2:balance = x   #balance = 8
        t2:x = balance - n   #0
        t2:balance = x    #0

    2.實際情況下:
        t1:x1 = balance + n  #x1 = 5

        t2:x2 = balance + n   #x2 = 8
        t2: balance = x2    #balance = 8

        t1:balance = x1   #balance = 5
        t1:x1 = balance - n  #x1 = 0
        t1:balance = x1   #balance = 0

        t2:x2 = balance - n #x2 = -8
        t2:balance = x2   #balance = -8

    總結:修改balance需要多條語句,執行多條語句的時候,線程隨時可能會被中斷,從而導致多個線程將一個變量修改亂了

解決:上鎖【給多線程中的臨界資源上一把鎖,當一個線程訪問的時候,其他的線程則必須在鎖外面進行等待,直到鎖被釋放之後,
其他線程纔有再次爭搶時間片的機會】
"""

線程鎖

from  threading import  Thread,Lock

balance = 0

#創建一個鎖對象
lock = Lock()

def change(n):
    global balance
    balance = balance + n
    balance = balance - n

#子線程的任務
def run(n):
    for _ in range(1000000):
        #獲取鎖
        lock.acquire()

        try:
            # 臨界資源
            change(n)
        finally:
            #釋放鎖
            lock.release()


if __name__ == "__main__":

    t1 = Thread(target=run,args=(5,))
    t2 = Thread(target=run,args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    #理論上,balance最終的結果爲0
    print(balance)
from  threading import  Thread,Lock

balance = 0

#創建一個鎖對象
lock = Lock()

def change(n):
    global balance
    balance = balance + n
    balance = balance - n

#子線程的任務
def run(n):
    for _ in range(1000000):
        #同樣表示獲取鎖,但是,不用手動釋放,當臨界資源的代碼執行完畢之後,鎖會被自動釋放
        with lock:
            # 臨界資源
            change(n)

if __name__ == "__main__":

    t1 = Thread(target=run,args=(5,))
    t2 = Thread(target=run,args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    #理論上,balance最終的結果爲0
    print(balance)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章