簡述程序,執行緒和協程
執行緒,程序和協程
執行緒
執行緒的概念
併發
任務數大於cpu核載,通過系統的各種任務跳讀演算法,是任務“在一起”執行任務! 假的多工
並行
任務數小於cpu核數,即任務真的在一起執行
多執行緒
1 同時執行
下面例子中test1和test2是同時執行
import threading import time def tes1(): for i in range(3): print("--test1--%d" % i) time.sleep(1) def tes2(): for i inrange(3): print("--test2--%d" % i) time.sleep(1) if __name__ == "__main__": t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() # 啟動執行緒,讓執行緒開始執行 t2.start() # 執行結果 --test1--0 --test2--0 (間隔1秒) --test1--1 --test2--1 (間隔1秒)--test1--2 --test2--2
2 順序執行
test1先執行,test2後執行
import threading import time def tes1(): for i in range(3): print("--test1--%d" % i) def tes2(): for i in range(3): print("--test2--%d" % i) if __name__ == "__main__": t1 = threading.Thread(target=test1) t2= threading.Thread(target=test2) t1.start() time.sleep(1) print("test1 is over") t2.start() time.sleep(1) print("test2 is over") # 執行結果 --test1--0 --test1--1 --test1--2 (間隔1秒) test1 over --test2--0 --test2--1 --test2--2 (間隔1面) test2 over
多執行緒全域性變數
全域性變數
import threading import time g_num = 100 def test1(): global g_num g_num += 1 print("--in test1 g_num=%d" % g_num) def test2(): print("--in test2 g_num=%d" % g_num) if __name__ == "__main__": t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() time.sleep(1) t2.start()+ time.sleep(1)
去函式那個筆記了解全域性變數
多執行緒全域性變數實參
import threading import time g_num = 100 def test1(temp): #傳遞實參的方式 temp.append(33) print("--in test1 temp=%s" % str(temp)) 545. def test2(): print("--in test2 temp=%s" % str(temp)) g_num = [11,22] if __name__ == "__main__": # 建立執行緒 t1 = threading.Thread(target=test1, args=(g_num,)) t2 = threading.Thread(target=test2, args=(g_num,)) t1.start() time.sleep(1) t2.start() time.sleep(1)
多執行緒共享全域性變數資源競爭
import threading import time g_num = 0 def test1(): glibal g_num for i in range(num): g_num += 1 print("--in test1 g_num=%d" % g_num) def test2(): glibal g_num for i in range(num): g_num += 1 print("--in test2 g_num=%d" % g_num) if __name__ == "__main__": # 建立執行緒 t1 = threading.Thread(target=test1,args=(100,)) t2 = threading.Thread(target=test2,args=(100,)) # t1 = threading.Thread(target=test1,args=(10000,)) # t2 = threading.Thread(target=test2,args=(10000,)) t1.start() t2.start()
多執行緒解決全域性變數資源競爭01
使用互斥鎖
import threading import time g_num = 0 def test1(): glibal g_num # 上鎖,如果之前沒有上鎖,那麼此時上鎖成功 # 如果之前上過鎖,那麼就會堵塞在這裡,知道這個鎖解開為止 muext.acquire() for i in range(num): g_num += 1 # 解鎖 muext.release() print("--in test1 g_num=%d" % g_num) def test2(): glibal g_num muext.acquire() for i in range(num): g_num += 1 muext.release() print("--in test2 g_num=%d" % g_num) # 建立一個互斥鎖,預設是沒有上鎖的 muext = threading.Lock() if __name__ == "__main__": t1 = threading.Thread(target=test1,args=(10000,)) t2 = threading.Thread(target=test2,args=(10000,)) t1.start() t2.start() # 上述程式碼上鎖和解鎖在for迴圈外,解釋:子執行緒t1和t2不知道誰先執行,假如是t1先執行,在上鎖的後,一直執行for迴圈,知道迴圈結束為止然後在解鎖,在執行t2此時全域性變數從0變成了10000,直到for迴圈結束解鎖變成2000,程式解鎖
多執行緒解決全域性變數資源競爭02
import threading import time g_num = 0 def test1(): glibal g_num for i in range(num): muext.acquire() g_num += 1 muext.release() print("--in test1 g_num=%d" % g_num) def test2(): glibal g_num for i in range(num): muext.acquire() g_num += 1 muext.release() print("--in test2 g_num=%d" % g_num) muext = threading.Lock() if __name__ == "__main__": t1 = threading.Thread(target=test1,args=(10000,)) t2 = threading.Thread(target=test2,args=(10000,)) t1.start() t2.start() # 如果上鎖和解鎖在for迴圈內部,不管t1和t2誰先執行,每次執行+1結束後解鎖,然後在分配t1和t2誰先執行,這個先後是沒有規律的,可能是t1執行很多次之後再是執行t2,可能反之,所以其中一個是for迴圈執行結束後得到20000,但是另外一個一定是執行10000後還在疊加
多工版UDP聊天
import socket import threading if __name__ == "__main__": # 建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 繫結埠 udp_socket.bind(("", 7788)) # 獲得對方ip和port dest_ip = input("請輸入ip:") dest_port = input("請輸入port:") # 接受資料 while True: recv_data = udp_socket.recvfrom(1024) print(recv_data) # 傳送資料 while True: send_data = input("輸入資料:") udp_socket.sendto(send_data.encode("utf-8"), dest_ip,dest_port) # 利用多執行緒 import socket import threading def recv_msg(udp_socket): # 接受資料 while True: recv_data = udp_socket.recvfrom(1024) #print(recv_data) print("[%s]:%s" %(recv_data[1], str(recv_data[0].decode("utf-8")))) # 顯示傳送的對方地址和資訊 def send_msg(udp_socket,dest_ip,dest_port): # 傳送資料 while True: send_data = input("輸入資料:") udp_socket.sendto(send_data.encode("utf-8"), dest_ip,dest_port) if __name__ == "__main__": # 建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 繫結埠 udp_socket.bind(("", 7788)) # 獲得對方ip和port dest_ip = input("請輸入ip:") dest_port = int(input("請輸入port:")) # 注意 t_recv = threading.Thread(target=recv_msg, args=(udp_socket,)) t_send = threading.Thread(target=send_msg, args=(udp_socket,dest_ip,dest_port)) t_recv.start() t_send.start()
程序
程序的概念
一個程式執行起來,程式碼+用到的資源稱之為程序,他是作業系統分配資源的基本單位元
匯入multiprocessing模組
子程序的傳遞引數
import multiprocessing # import os # 匯入路徑模組 import time def test(a,b,c,*args, **kwargs): print(a) print(b) print(c) print(args) # 拆包(元組) print(kwargs) # 拆包(字典) if __name__ == "__main__": p = multiprocessing.Process(target=test, args=(1,2,3,4,5,6,7,8), kwargs={"name":"Lily"}) # 當匯入元素和元組的時候,統一以元組的形式匯入 # 當傳入kwargs的時候,在建立p類的時候,傳入字典形式 p.start()
多程序之間不共享全域性變數
程序之間是兩個獨立的程式不共享全域性變數
import multiprocessing import time nums = [1,2,3] #設定全域性變數 def test1(): nums.append(4) # 利用方法改變全域性變數 print("在test1中nums=%s" % str(nums)) def test2(): print("在test2中nums=%s" % str(nums)) if __name__ == "__main__": # 建立程序 p1 = multiprocessing.Proess(target=test1) p2 = multiprocessing.Proess(target=test2) p1.start() p1.join() # 確保p1在執行結束後再執行p2 time.sleep(1) # 或者是在p1執行後停頓1秒 p2.start() # 得出結果是test1中[1,2,3,4], test2中[1,2,3] # 在程序中不共享全域性變數
程序和執行緒的區別
簡單的對比
程序:能夠完成多工,例如一臺電腦上可以執行多個QQ
(程序是系統進行資源的分配和排程的一個獨立單位)
執行緒:能夠完成多工,例如在同一個QQ可以開多個聊天視窗
(執行緒屬於程序,是程序的一個實體,是cpu排程和分配的基本單位,能更小的獨立執行的基本單位,執行緒自己使用系統資源)
區別
1 一個程式至少有一個程序,一個程序至少有一個執行緒
2 執行緒的劃分尺度小於程序(就是佔用資源比程序少)
3 程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,提高工作效率
4 執行緒不能獨立的執行,必須依存在程序中
5 程序就好比工廠的流水線,執行緒就好比流水線的員工
程序之間的通訊-Queue佇列
例項(僅限一臺電腦或是伺服器中的兩個程序之間的資料共享)
import multiprocessing # 一個程序向Queue中寫入資料,另一個程序從Queue獲取資料 # 通過Queue完成了多歌需要配合程序間的資料共享 def down(q): # 下載資料 # 模擬在網上下載資料,就簡單的建立下載好的列表 data = [1,2,3,4] # 向佇列寫入資料 for temp in data: q.put(temp) print("--資料已經存到佇列中---") def fenxi(q): # 資料處理 fenxi_data = list() #或者是fenxi_list = [] # 向儲存好的佇列中獲取資料 while True: new_data = q.get() # new_data是獲取的資料 fenxi_data.append(new_data) # 判斷:如果隊被取空的話,就退出 if q.empty(): break if __name__ == "__main__": # 建立一個佇列 q = multiprocessing.Queue() # 建立程序 # 傳入實參佇列在建立的子程序中呼叫q p1 = multiprocessing.Process(target=down, args=(q,)) p2 = muultiprocessing.Proess(target=fenxi, args=(q,)) p1.start() # p1.join() p2.start() # 以上操作有問題,建立的子程序p1和p2,不能確定那個子程序先執行,會導致p1還沒有下載好,p2就直接獲取資料,所以在p1.start()後新增一個p1.join(), p1.join()的功能就是讓p1執行完之後再執行其他的程序
補充說明
初始化Queue()物件時(例如q=Queue()),有下列方法
q.put() # 向佇列匯入寫入資料
q.get() # 向佇列下載獲取資料
q.empty() # 如果佇列是空的,返回True, 反之False
q.full() # 如果列隊滿了,返回True,反之False
程序池
程序之間的通訊
程序間通訊就是在不同程序之間傳播或交換資訊,那麼不同程序之間存在著什麼雙方都可以訪問的介質呢?程序的使用者空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享記憶體區。但是,系統空間卻是“公共場所”,所以核心顯然可以提供這樣的條件。除此以外,那就是雙方都可以訪問的外設了。在這個意義上,兩個程序當然也可以通過磁碟上的普通檔案交換資訊,或者通過“登錄檔”或其它資料庫中的某些表項和記錄交換資訊。廣義上這也是程序間通訊的手段,但是一般都不把這算作“程序間通訊”。因為那些通訊手段的效率太低了,而人們對程序間通訊的要求是要有一定的實時性。
程序間通訊主要包括管道, 系統IPC(包括訊息佇列,訊號量,共享儲存), SOCKET.
程序和執行緒區別
定義的區別:程序是系統進行資源分配和排程的一個獨立的單位;執行緒是程序的實體,是cpu排程和分配的基本單位
一個程式至少有一個程序,一個程序至少有一個執行緒;
執行緒的劃分尺度小於程序(資源比程序少),使得多執行緒程式的併發性高。
程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率
線執行緒不能夠獨立執行,必須依存在程序中
可以將程序理解為工廠中的一條流水線,而其中的執行緒就是這個流水線上的工人
優缺點:執行緒執行開銷小,效率高,,但不利於資源的管理和保護;而程序正相反。
協程
協程概念
用到更少的資源,:在一個執行緒中的某個函式,可以在任何地方儲存當前函式的一 些臨時變數等資訊,然後切換到另外一個函式中執行,注意不是通過呼叫函 數的方式做到的,並且切換的次數以及什麼時候再切換到原來的函式都由開 發者自己確定
協程和執行緒的區別
在實現多工時, 執行緒切換從系統層面遠不止儲存和恢復 CPU上下文這麼簡 單。 作業系統為了程式執行的高效性每個執行緒都有自己快取Cache等等數 據,作業系統還會幫你做這些資料的恢復操作。 所以執行緒的切換非常耗性 能。但是協程的切換隻是單純的操作CPU的上下,所以⼀秒鐘切換個上百 萬次系統都抗的住。
如何實現協程
1 生成器實現簡單的協程
import time def f1(): while True: print("---1----") yield time.sleep(1) def f2(): while True: print("---2----") yield time.sleep(1) if __name__ == "__main__": ff1 = f1() ff2 = f2() while True: next(ff1) next(ff2)
2 greenlet實現協程 (瞭解)
from greenlet import greenlet import time def w1(): while True: print("---1---") ww1.switch() # 使用greenlet模組中swtich()方法 time.sleep(1) def w2(): while True: print("---1---") ww2.switch() # 使用greenlet模組中swtich()方法 time.sleep(1) if __name__ == "__main__": ww1 = greenlet(w1) ww2 = greenlet(w2) ww1.switch()
3 gevent實現協程(重要)
import gevent def f(n): for i in range(n): print(gevent.getcurrent(), i) g1 = gevent.spawn(f,5) g2 = gevent.spawn(f,5) g3 = gevent.spawn(f,5) g1.join() g2.join() g3.join() # 執行結果 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 0 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 1 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 2 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 3 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 4 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 0 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 1 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 2 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 3 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 4 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 0 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 1 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 2 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 3 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 4 # 達成任務切換 import gevent def f(n): for i in range(n): print(gevent.getcurrent(), i) # 用來模擬一個耗時操作,不是用time模組 gevent.sleep(1) g1 = gevent.spawn(f,5) g2 = gevent.spawn(f,5) g3 = gevent.spawn(f,5) g1.join() g2.join() g3.join() # 執行結果 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 0 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 0 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 0 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 1 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 1 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 1 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 2 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 2 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 2 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 3 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 3 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 3 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 4 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 4 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 4
程序,執行緒和協程的對比(面試重點)
A 程序是資源分配的單位
B 執行緒是作業系統排程的單位
C 程序切換需要的資源最大,效率低
D 執行緒切換需要的資源一般,效率也很一般
E 協程切換任務資源小,效率高
F 多程序,多執行緒根據cpu的核數不一樣可能並行,但是協程是在一個執行緒中,所以是併發的
G 程序不共享資源,執行緒共享資源
GIL(全域性直譯器鎖)(面試重點)
每個執行緒在執行過程中都需要先獲取GIL,保證同一時刻只有一個執行緒可以執行程式碼,所以執行緒是併發的,都是講併發執行成序列,由此來控制同一時間內共享資料只能被一個任務修改,進而保證資料的安全!
底層知識
因為python的執行緒是呼叫作業系統的原生執行緒,這個原生執行緒就是C語言寫的原生執行緒。因為python是用C寫的,啟動的時候就是呼叫的C語言的介面。因為啟動的C語言的遠端執行緒,那它要調這個執行緒去執行任務就必須知道上下文,所以python要去調C語言的介面的執行緒,必須要把這個上限問關係傳給python,那就變成了一個在加減的時候要讓程式序列才能一次計算。就是先讓執行緒1,再讓執行緒2.......
多執行緒用於IO密集型,如socket,爬蟲,web
多程序用於計算密集型,如金融分析
互斥鎖(面試重點)
當多個執行緒幾乎同時修改一個共享資料的時候,需要進行同步控制,執行緒同步能夠保證多個執行緒安全的競爭資源,最簡單的同步機制就是引入互斥鎖
如何執行:某個執行緒需要更改共享資料的時候,先鎖定,此時資源狀態為鎖定狀態,其他執行緒不能更改,直到該執行緒釋放資源,將資源的狀態變成非鎖定狀態,其他執行緒才能再次鎖定該資源,互斥鎖保證每次只有一個執行緒進行操作,從而保證多執行緒情況的資料正確性!
優點:確保某段關鍵程式碼只能有一個執行緒從頭到尾完整的執行
缺點:A--阻止了多執行緒的併發,包含鎖的某段程式碼只能以單執行緒的模式執行,效率大打折扣。B--由於可以存在多個鎖,不同的執行緒持有不同的鎖,並試圖獲取對方的鎖,可能會造成死鎖!