1. 程式人生 > 實用技巧 >程序與執行緒

程序與執行緒

程序與執行緒

程序

程序的建立

  1. 和執行緒使用方式基本一樣,只不過匯入的模組不同
  2. 匯入multiprocessing模組
  3. 通過multiprocessing.Process(target=work1)指定執行的函式,然後start
  4. 主程序結束後,子程序不會退出

程序可以實現多工(主程序結束之後,不會結束子程序,不過會有特殊情況)

import multiprocessing


def doing1():
while True:
print("doing1")


def doing2():
while True:
print("doing2")


def main():
# 建立程序1
th_name1 = multiprocessing.Process(target=doing1)
th_name1.start()
# 建立程序2
th_name2 = multiprocessing.Process(target=doing2)
th_name2.start()


if __name__ == '__main__':
main()

程序之間不共享全域性變數

  1. 程序和執行緒在共享全域性變數上面是有區別的
  2. 程序之間不會共享全域性變數
  3. 程序開啟後,相當於把當前整個程式碼複製一份,所以這個子程序是知道num之前的值的
import multiprocessing
import time

num = [11, 12]


def doing1():
for i in range(3):
num.append(i)
print("doing1=%s" %num)

def doing2():
print(num)


def main():
p1 = multiprocessing.Process(target=doing1)
p1.start()
time.sleep(1)
p2 = multiprocessing.Process(target=doing2)
p2.start()


if __name__ == '__main__':
main()

子程序傳遞引數

子程序也是可以傳引數的

利用args或者kwargs可以傳遞引數,並且args裡面傳的是元祖

import multiprocessing


def work(num):
for i in range(num):
print("輸出")


# 程序需要在main中執行
# 執行3次
def main():
p = multiprocessing.Process(target=work, args=(3,))
# 指定引數執行3次
# p = multiprocessing.Process(target=work,kwargs={"num":3})
p.start()


if __name__ == '__main__':
main()

程序和執行緒的區別

  1. 功能
    1. 程序 能夠完成多工,比如在一臺電腦上能夠同時執行多個QQ
    2. 執行緒 能夠完成多工,比如一個QQ中的多個聊天視窗
  2. 定義的不同
    1. 程序是系統進行資源分配和排程的一個獨立單位
    2. 執行緒是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位,執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個程序的其他的執行緒共享程序所擁有的全部資源
  3. 區別
    1. 一個程式至少有一個程序,一個程序至少有一個執行緒
    2. 執行緒的劃分尺度小於程序(資源比程序少),使得多執行緒程式的併發性搞
    3. 程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大的提高了程式的執行效率
    4. 執行緒不能夠獨立執行,必須依存在程序中
  4. 優缺點
    1. 執行緒執行開銷小,單不利於資源的管理和保護
    2. 程序正相反
  5. 程序負責分配資源,執行緒負責做事情.

程序間通訊

程序間通訊有很多種 Queue(先進先出)、socket、檔案等

基本的使用方式

# 建立
q=multiprocessing.Queue([maxsize])
# 放資料
q.put(xxx)
# 取資料
q.get()

示例

import multiprocessing
import time


def doing1(q):
str = "laowang"
for i in str:
q.put(i)

def doing2(q):
while True:
result=q.get()
print(result)
def main():
# 建立佇列物件
q=multiprocessing.Queue()
p1 = multiprocessing.Process(target=doing1,args=(q,))
p1.start()
time.sleep(1)
p2 = multiprocessing.Process(target=doing2,args=(q,))
p2.start()


if __name__ == '__main__':
main()

queue的引數和函式

maxsize引數可以指定佇列物件最多放多少個數據

qsize函式可以獲取當前對立中資料的個數

get和put方法都有一個timeout引數,可以防止這種情況

import multiprocessing

# 限制存與取3個
q = multiprocessing.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

程序池

  1. 當需要建立的子程序數量不多時,可以直接利用multprocessing中的Process動態生成多個程序但是如果是上百甚至上千個目標,手動的去建立程序的工作量巨大,此時就可以用到multprocessing模組提供的Pool方法
  2. 初始化Pool時,可以指定一個最大程序數,當有新的請求提交到Pool中時,如果池還沒有滿,那麼就會建立一個新的程序用來執行該請求,單如果池中的程序數已經達到指定的最大值,那麼該請求就會等待,直到池中有程序結束,才會用之前的程序來執行新的任務
import multiprocessing
import time


def work():
print("test")
time.sleep(1)


def main():
# 3個程序
p = multiprocessing.Pool(3)
for i in range(10):
p.apply_async(work)

time.sleep(5)
print("over...")


if __name__ == '__main__':
main()

程序池 -close和join函式

  1. 如果主程序使用程序池的子程序,當主程序沒有程式碼執行的時候,那麼程序池中的子程序也會跟著直接結束(使用普通的Process建立不會出現這個問題)
  2. close函式表示不再向程序池中新增任務
  3. join表示等待子程序程式碼執行完畢
import multiprocessing
import time

num = [11, 12]


def doing1():
for i in range(3):
num.append(i)
print("doing1=%s" % num)


def main():
p1 = multiprocessing.Pool(10)
for i in range(10):
p1.apply_async(doing1)
p1.close()
p1.join()


if __name__ == '__main__':
main()

程序編號-pid

  1. pid,程序編號
  2. 通過匯入os模組,使用getpid()函式進行獲取 os.getpid()

程序池間的通訊

  1. 程序池間的通訊和程序間通訊很相似,但又有區別
  2. 程序間通訊使用的是multiprocessing.Queue()
  3. 程序池間通訊使用的是multiprocessing.Manager().Queue()
  4. 關於Queue物件的用法是一致的
import multiprocessing


def doing1(q):
print(q.get())


def main():
# 建立佇列物件
q = multiprocessing.Manager().Queue()
q.put([11, 22, 33])
p1 = multiprocessing.Pool(3)
p1.apply_async(doing1, args=(q,))
p1.close()
p1.join()


if __name__ == '__main__':
main()

執行緒

執行緒-鎖

執行緒-join函式

多執行緒的好處是可以共享全域性變數,一起操作同一個東西,但是如果操作不好,可能會有問題.

import threading

num = 0


def doing1():
global num
for i in range(1000000):
num += 1
print("doing1=", num)


def doing2():
global num
for i in range(1000000):
num += 1
print("doing2=", num)


def main():
th_num1 = threading.Thread(target=doing1)
th_num2 = threading.Thread(target=doing2)
th_num1.start()
# 等待這個執行緒完事兒之後,再往後執行
th_num1.join()
th_num2.start()


if __name__ == '__main__':
main()

雖然問題解決了,但是這個join是一個偽執行緒,因為需要等待第一個執行緒執行完畢之後,在執行第二個,意味著跟單執行緒沒什麼區別

程序之間是不會共享全域性變數

互斥鎖

務必注意互斥鎖的位置,如果用不好,其實就跟join的效果是一樣的,threading模組中定義了Lock類,可以方便的處理鎖定:

import threading

num = 0
mutex = None


def doing1():
global num
for i in range(1000000):
# 在迴圈內部加鎖,就是在每次迴圈一次就加鎖
mutex.acquire()
num += 1
# 解鎖
mutex.release()
print("doing1=%s" % num)


def doing2():
global num
for i in range(1000000):
# 在迴圈內部加鎖
mutex.acquire()
num += 1
# 解鎖
mutex.release()
print("doing2=%s" % num)


def main():
# 將mutex作為全域性變數
global mutex
# 建立鎖的物件
mutex = threading.Lock()
th_num1 = threading.Thread(target=doing1)
th_num2 = threading.Thread(target=doing2)
th_num1.start()
th_num2.start()


if __name__ == '__main__':
main()

上鎖解鎖過程

  1. 當一個執行緒呼叫鎖的acquire()方法獲得鎖時,鎖就進入locked狀態
  2. 每次只有一個執行緒可以獲得鎖,如果此時另一個執行緒檢視獲得這個鎖,該執行緒就會變為blocked狀態,稱為阻塞,知道擁有鎖的執行緒呼叫鎖的release()方法釋放鎖之後,鎖進unlocked狀態
  3. 執行緒排程程式從處於同步阻塞狀態的執行緒中選擇一個來獲得鎖,並使得該執行緒進入執行狀態

鎖的引數和返回值

  1. 互斥鎖是可以有引數和返回值的
  2. 返回值表示上鎖是否成功
  3. blocking引數表示,是否是堵塞狀態
  4. timeout引數表示,阻塞多少秒之後,變成非阻塞
import threading

multx = threading.Lock()
for i in range(5):
x = multx.acquire(blocking=True, timeout=3) # 只有在blocking為True的時候,使用timeout
if x:
print("加鎖成功")
else:
print("加鎖失敗")

小總結

  1. 如果是阻塞狀態,那麼會進行等待,不會有返回值
  2. 如果不是阻塞狀態,那麼返回值就是上鎖成不成功

執行緒-死鎖

  1. 互斥鎖,是一種鎖
  2. 死鎖,是一種現象,不是一個東西
  3. 一般情況下,有多個鎖,才可能出現死鎖的現象,說白了如果說兩個鎖,a和b,那麼a鎖後執行的程式碼等著b解鎖,b鎖後執行的程式碼等著a解鎖,這就是一種死鎖
  4. 避免死鎖的辦法,就是新增等待時間

執行緒-鎖的優缺點

  1. 鎖的好處 確保了某段關鍵程式碼只能由一個執行緒從頭到尾完整的執行
  2. 鎖的壞處 阻止了多執行緒併發執行,包含鎖的某段程式碼實際上只能以單執行緒模式執行,效率就打大的下降了,由於可以存在多個鎖,不同的執行緒持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖

多工原理

  1. 什麼叫多工? 簡單地說,就是作業系統可以同時執行多個任務,你一邊在用瀏覽器上網,一邊在聽MP3,一邊在用word趕作業這就是多工,至少同時有3個任務正在執行,還有很多工悄悄地在後臺同時執行著,只是桌面上沒有顯示而已
  2. 單核cpu的工作原理
    1. 現在多核cpu已經非常普及了,但是即使過去的單核cpu,也可以執行多工,由於cpu執行程式碼都是順序執行的,那麼單核cpu是怎麼執行多工呢?
    2. 也就是作業系統輪流讓各個任務交替執行,任務1執行0.01秒,切換到任務2,任務2執行0.01秒,在切換到任務3,執行0.01秒...這樣反覆執行下去,表面上看,每個任務都是交替執行的,但是,由於cpu的執行速度實在是太快,我們感覺就像所有任務都在同時執行一樣.
    3. 整整的並行執行多工只能在多核cpu上實現,但是由於任務數量遠遠多於cpu的核心數量,所以作業系統也會自動把很多工輪流排程到每個核心上執行
  3. 多核cpu工作原理 和單核類似,相當於多了一個幹活的人
  4. 併發 指的是任務書多餘cpu核數,通過作業系統的各種任務排程演算法,實現用多個任務一起執行(實際上總有一些任務不在執行,因為切換任務的速度相當快,看上去一起執行而已)
  5. 並行 值的是任務書小於等於cpu核數,即任務真的是一起執行的
  6. 並行和併發都算是多工單並行實際上才是真正的多工,併發是假的

執行緒的兩種建立方式

  1. 直接使用threading模組的Thread類,指定要執行的方法,在呼叫start
  2. 使用繼承的方式,繼承Thread類,重新run方法,建立這個物件後,在呼叫start

檢視當前程式執行緒數量

threading.enumerate()

  1. 獲取所有執行緒,返回的是一個列表
  2. 如果需要個數,使用len(threading.enumerate())

搶票案例

import threading
import time

num = 1000000
lock = None
fre_1 = 0
fre_2 = 0


def work1():
global num, fre_1
while True:
lock.acquire()
if num == 0:
break
else:
num -= 1
fre_1 += 1
lock.release()


def work2():
global num, fre_2
while True:
lock.acquire()
if num == 0:
break
else:
num -= 1
fre_2 += 1
lock.release()


def main():
global lock
lock = threading.Lock()
tr1 = threading.Thread(target=work1)
tr1.start()
tr2 = threading.Thread(target=work2)
tr2.start()
# 需要設定等待時間
time.sleep(2)
print("work1賣了:%d張" % fre_1)
print("work2賣了:%d張" % fre_2)


if __name__ == '__main__':
main()

執行緒的其他內容