1. 程式人生 > >簡述程序,執行緒和協程

簡述程序,執行緒和協程

執行緒,程序和協程

執行緒

執行緒的概念

併發

任務數大於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 in
range(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--由於可以存在多個鎖,不同的執行緒持有不同的鎖,並試圖獲取對方的鎖,可能會造成死鎖!