1. 程式人生 > >程序、執行緒總結

程序、執行緒總結

Resource

1、程序(process

狹義定義:程序就是一段程式的執行過程。

廣義定義:程序是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是作業系統動態執行的基本單元,在傳統的作業系統中,程序既是基本的分配單元,也是基本的執行單元。

簡單的來講程序的概念主要有兩點

  第一,程序是一個實體。每一個程序都有它自己的地址空間,一般情況下,包括:

    文字區域(text region),本區域儲存處理器執行的程式碼;

    資料區域(data region),儲存變數和程序執行期間使用的動態分配的記憶體;

    堆疊(stack region),儲存著活動過程呼叫的指令和本地變數。

  第二,程序是一個“執行中的程式”。程式是一個沒有生命的實體,只有處理器賦予程式生命時,它才能成為一個活動的實體,我們稱其為程序。

程序狀態:執行、阻塞、掛起阻塞、就緒、掛起就緒

  就緒狀態其實就是獲取了出cpu外的所有資源,只要處理器分配資源就可以馬上執行。就緒狀態有排隊序列什麼的,排隊原則不再贅述。執行態就是獲得了處理器分配的資源,程式開始執行。阻塞態,當程式條件不夠時候,需要等待條件滿足時候才能執行,如等待i/o操作時候,此刻的狀態就叫阻塞態。

狀態之間的轉換:

  • (1)準備就緒的程序,被CPU排程執行,變成執行態;

  • (2)執行中的程序,進行I/O請求或者不能得到所請求的資源,變成阻塞態;

  • (3)執行中的程序,程序執行完畢(或時間片已到),變成就緒態;

  • (4)將阻塞態的程序掛起,變成掛起阻塞態,當導致程序阻塞的I/O操作在使用者重啟程序前完成(稱之為喚醒),掛起阻塞態變成掛起就緒態,當用戶在I/O操作結束之前重啟程序,掛起阻塞態變成阻塞態;

  • (5)將就緒(或執行)中的程序掛起,變成掛起就緒態,當該程序恢復之後,掛起就緒態變成就緒態;

2、程式

  說起程序,就不得不說下程式。先看定義:程式是指令和資料的有序集合,其本身沒有任何執行的含義,是一個靜態的概念。而程序則是在處理機上的一次執行過程,它是一個動態的概念。這個不難理解,其實程序是包含程式的,程序的執行離不開程式,程序中的文字區域就是程式碼區,也就是程式。

3、執行緒

  通常在一個程序中可以包含若干個執行緒,當然一個程序中至少有一個執行緒,不然沒有存在的意義。執行緒可以利用程序所擁有的資源,在引入執行緒的作業系統中,通常都是把程序作為分配資源的基本單位,而把執行緒作為獨立執行和獨立排程的基本單位,由於執行緒比程序更小,基本上不擁有系統資源,故對它的排程所付出的開銷就會小得多,能更高效的提高系統多個程式間併發執行的程度。

執行緒是程序中執行運算的最小單位,是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。

好處

(1)易於排程。

(2)提高併發性。通過執行緒可方便有效地實現併發性。程序可建立多個執行緒來執行同一程式的不同部分。

(3)開銷少。建立執行緒比建立程序要快,所需開銷很少

4、多執行緒

  在一個程式中,這些獨立執行的程式片段叫作“執行緒”(Thread),利用它程式設計的概念就叫作“多執行緒處理”。多執行緒是為了同步完成多項任務,不是為了提高執行效率,而是為了提高資源使用效率來提高系統的效率。執行緒是在同一時間需要完成多項任務的時候實現的。

  最簡單的比喻多執行緒就像火車的每一節車廂,而程序則是火車。車廂離開火車是無法跑動的,同理火車也不可能只有一節車廂。多執行緒的出現就是為了提高效率。

5、執行緒和程序的關係以及區別?

** 程序和執行緒的關係:**
  • (1)一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒。

  • (2)資源分配給程序,同一程序的所有執行緒共享該程序的所有資源。

  • (3)處理機分給執行緒,即真正在處理機上執行的是執行緒

  • (4)執行緒在執行過程中,需要協作同步。不同程序的執行緒間要利用訊息通訊的辦法實現同步。執行緒是指程序內的一個執行單元,也是程序內的可排程實體.

程序與執行緒的區別
  • (1)排程:執行緒作為排程和分配的基本單位,程序作為擁有資源的基本單位

  • (2)併發性:不僅程序之間可以併發執行,同一個程序的多個執行緒之間也可併發執行

  • (3)擁有資源:程序是擁有資源的一個獨立單位,執行緒不擁有系統資源,但可以訪問隸屬於程序的資源.

  • (4)系統開銷:在建立或撤消程序時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤消執行緒時的開銷。

6、程序間通訊的方式?

  • (1)管道(pipe)及有名管道(named pipe):管道可用於具有親緣關係的父子程序間的通訊,有名管道除了具有管道所具有的功能外,它還允許無親緣關係程序間的通訊。

  • (2)訊號(signal):訊號是在軟體層次上對中斷機制的一種模擬,它是比較複雜的通訊方式,用於通知程序有某事件發生,一個程序收到一個訊號與處理器收到一箇中斷請求效果上可以說是一致的。

  • (3)訊息佇列(message queue):訊息佇列是訊息的連結表,它克服了上兩種通訊方式中訊號量有限的缺點,具有寫許可權得程序可以按照一定得規則向訊息佇列中新增新資訊;對訊息佇列有讀許可權得程序則可以從訊息佇列中讀取資訊。

  • (4)共享記憶體(shared memory):可以說這是最有用的程序間通訊方式。它使得多個程序可以訪問同一塊記憶體空間,不同程序可以及時看到對方程序中對共享記憶體中資料得更新。這種方式需要依靠某種同步操作,如互斥鎖和訊號量等。

  • (5)訊號量(semaphore):主要作為程序之間及同一種程序的不同執行緒之間得同步和互斥手段。

  • (6)套接字(socket):這是一種更為一般得程序間通訊機制,它可用於網路中不同機器之間的程序間通訊,應用非常廣泛。

7、同步和互斥的區別:

  • 當有多個執行緒的時候,經常需要去同步這些執行緒以訪問同一個資料或資源。例如,假設有一個程式,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案中的字元數。當然,在把整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把兩個執行緒當作是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解決此問題,你必須使兩個執行緒同步工作。

  • 所謂同步,是指散步在不同程序之間的若干程式片斷,它們的執行必須嚴格按照規定的某種先後次序來執行,這種先後次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。

  • 所謂互斥,是指散佈在不同程序之間的若干程式片斷,當某個程序執行其中一個程式片段時,其它程序就不能執行它們之中的任一程式片段,只能等到該程序執行完這個程式片段後才可以執行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

CODE

Python的Threading模組

  • Threading用於提供執行緒相關的操作
import threading
import time

def show(arg):
    time.sleep(1)
    print 'thread'+str(arg)

for i in range(10):
    t = threading.Thread(target=show, args=(i,))
    t.start()

print 'main thread stop'
更多方法:
  • start 執行緒準備就緒,等待CPU排程
  • setName 為執行緒設定名稱
  • getName 獲取執行緒名稱
  • setDaemon 設定為後臺執行緒或前臺執行緒(預設);如果是後臺執行緒,主執行緒執行過程中,後臺執行緒也在進行,主執行緒執行完畢後,後臺執行緒不論成功與否,均停止;如果是前臺執行緒,主執行緒執行過程中,前臺執行緒也在進行,主執行緒執行完畢後,等待前臺執行緒也執行完成後,程式停止
  • join 逐個執行每個執行緒,執行完畢後繼續往下執行,該方法使得多執行緒變得無意義
  • run 執行緒被cpu排程後自動執行執行緒物件的run方法
  • Lock 執行緒鎖(互斥鎖Mutex)
  • Event
部分方法示例:
Daemon(守護程序)
import time
import threading

def run(n):

    print('[%s]------running----\n' % n)
    time.sleep(2)
    print('--done--')

def main():
    for i in range(5):
        t = threading.Thread(target=run,args=[i,])
        #time.sleep(1)
        t.start()
        t.join(1)
        print('starting thread', t.getName())


m = threading.Thread(target=main,args=[])
m.setDaemon(True) #將主執行緒設定為Daemon執行緒,它退出時,其它子執行緒會同時退出,不管是否執行完任務
m.start()
#m.join(timeout=2)
print("---main thread done----")
執行緒鎖(互斥鎖Mutex)

一個程序下可以啟動多個執行緒,多個執行緒共享父程序的記憶體空間,也就意味著每個執行緒可以訪問同一份資料,此時,如果2個執行緒同時要修改同一份資料,會出現什麼狀況?

鎖的使用
    • 建立鎖
      mutex = threading.Lock()
    • 鎖定
      mutex.acquire([timeout])
    • 釋放
      mutex.release()
import time
import threading

def addNum():
    global num #在每個執行緒中都獲取這個全域性變數
    print('--get num:',num )
    time.sleep(1)
    num  -=1 #對此公共變數進行-1操作

num = 100  #設定一個共享變數
thread_list = []
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list: #等待所有執行緒執行完畢
    t.join()


print('final num:', num )

正常來講,這個num結果應該是0, 但在python 2.7上多執行幾次,會發現,最後打印出來的num結果不總是0,為什麼每次執行的結果不一樣呢? 哈,很簡單,假設你有A,B兩個執行緒,此時都 要對num 進行減1操作, 由於2個執行緒是併發同時執行的,所以2個執行緒很有可能同時拿走了num=100這個初始變數交給cpu去運算,當A執行緒去處完的結果是99,但此時B執行緒運算完的結果也是99,兩個執行緒同時CPU運算的結果再賦值給num變數後,結果就都是99。那怎麼辦呢? 很簡單,每個執行緒在要修改公共資料時,為了避免自己在還沒改完的時候別人也來修改此資料,可以給這個資料加一把鎖, 這樣其它執行緒想修改此資料時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此資料。

注:不要在3.x上執行,不知為什麼,3.x上的結果總是正確的,可能是自動加了鎖

加鎖版本:

import time
import threading

def addNum():
    global num #在每個執行緒中都獲取這個全域性變數
    print('--get num:',num )
    time.sleep(1)
    lock.acquire() #修改資料前加鎖
    num  -=1 #對此公共變數進行-1操作
    lock.release() #修改後釋放

num = 100  #設定一個共享變數
thread_list = []
lock = threading.Lock() #生成全域性鎖
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list: #等待所有執行緒執行完畢
    t.join()

print('final num:', num )
RLock(遞迴鎖)

說白了就是在一個大鎖中還要再包含子鎖

import threading,time

def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num +=1
    lock.release()
    return num
def run2():
    print("grab the second part data")
    lock.acquire()
    global  num2
    num2+=1
    lock.release()
    return num2
def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res,res2)


if __name__ == '__main__':

    num,num2 = 0,0
    lock = threading.RLock()
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()

while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num,num2)
Semaphore(訊號量)

互斥鎖 同時只允許一個執行緒更改資料,而Semaphore是同時允許一定數量的執行緒更改資料 ,比如廁所有3個坑,那最多隻允許3個人上廁所,後面的人只能等裡面有人出來了才能再進去。

import threading,time

def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" %n)
    semaphore.release()

if __name__ == '__main__':

    num= 0
    semaphore  = threading.BoundedSemaphore(5) #最多允許5個執行緒同時執行
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()

while threading.active_count() != 1:
    pass #print threading.active_count()
else:
    print('----all threads done---')
    print(num)
Events執行緒間通訊

Python提供了Event物件用於執行緒間通訊,它是由執行緒設定的訊號標誌,如果訊號標誌位真,則其他執行緒等待直到訊號接觸。

Event物件實現了簡單的執行緒通訊機制,它提供了設定訊號,清楚訊號,等待等用於實現執行緒間的通訊。

Events的使用
  • event = threading.Event()
  • event.wait()

Event物件wait的方法只有在內部訊號為真的時候才會很快的執行並完成返回。當Event物件的內部訊號標誌位假時,則wait方法一直等待到其為真時才返回。

  • event.set()

使用Event的set()方法可以設定Event物件內部的訊號標誌為真。Event物件提供了isSet()方法來判斷其內部訊號標誌的狀態。當使用event物件的set()方法後,isSet()方法返回真

  • event.clear()

使用Event物件的clear()方法可以清除Event物件內部的訊號標誌,即將其設為假,當使用Event的clear方法後,isSet()方法返回假