Python進程和線程

引入進程和線程的概念及區別

一、線程的基本概念

概念

線程是進程中執行運算的最小單位,是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。一個線程能夠建立和撤消另外一個線程,同一進程中的多個線程之間能夠併發執行。
************************python

好處
  • (1)易於調度。算法

  • (2)提升併發性。經過線程可方便有效地實現併發性。進程可建立多個線程來執行同一程序的不一樣部分。網絡

  • (3)開銷少。建立線程比建立進程要快,所需開銷不多多線程

二、進程的基本狀態及狀態之間的關係

狀態:運行、阻塞、掛起阻塞、就緒、掛起就緒併發

狀態之間的轉換:app

  • (1)準備就緒的進程,被CPU調度執行,變成運行態;socket

  • (2)運行中的進程,進行I/O請求或者不能獲得所請求的資源,變成阻塞態;高併發

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

  • (4)將阻塞態的進程掛起,變成掛起阻塞態,當致使進程阻塞的I/O操做在用戶重啓進程前完成(稱之爲喚醒),掛起阻塞態變成掛起就緒態,當用戶在I/O操做結束以前重啓進程,掛起阻塞態變成阻塞態;操作系統

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

三、線程和進程的關係以及區別?

** 進程和線程的關係:**

  • (1)一個線程只能屬於一個進程,而一個進程能夠有多個線程,但至少有一個線程。

  • (2)資源分配給進程,同一進程的全部線程共享該進程的全部資源。

  • (3)處理機分給線程,即真正在處理機上運行的是線程

  • (4)線程在執行過程當中,須要協做同步。不一樣進程的線程間要利用消息通訊的辦法實現同步。線程是指進程內的一個執行單元,也是進程內的可調度實體.

進程與線程的區別:

  • (1)調度:線程做爲調度和分配的基本單位,進程做爲擁有資源的基本單位

  • (2)併發性:不只進程之間能夠併發執行,同一個進程的多個線程之間也可併發執行

  • (3)擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但能夠訪問隸屬於進程的資源.

  • (4)系統開銷:在建立或撤消進程時,因爲系統都要爲之分配和回收資源,致使系統的開銷明顯大於建立或撤消線程時的開銷。

四、進程間通訊的方式?

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

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

  • (3)消息隊列(message queue):消息隊列是消息的連接表,它克服了上兩種通訊方式中信號量有限的缺點,具備寫權限得進程能夠按照必定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則能夠從消息隊列中讀取信息。

  • (4)共享內存(shared memory):能夠說這是最有用的進程間通訊方式。它使得多個進程能夠訪問同一塊內存空間,不一樣進程能夠及時看到對方進程中對共享內存中數據得更新。這種方式須要依靠某種同步操做,如互斥鎖和信號量等。

  • (5)信號量(semaphore):主要做爲進程之間及同一種進程的不一樣線程之間得同步和互斥手段。

  • (6)套接字(socket):這是一種更爲通常得進程間通訊機制,它可用於網絡中不一樣機器之間的進程間通訊,應用很是普遍。

五、同步和互斥的區別:

  • 當有多個線程的時候,常常須要去同步這些線程以訪問同一個數據或資源。例如,假設有一個程序,其中一個線程用於把文件讀到內存,而另外一個線程用於統計文件中的字符數。固然,在把整個文件調入內存以前,統計它的計數是沒有意義的。可是,因爲每一個操做都有本身的線程,操做系統會把兩個線程看成是互不相干的任務分別執行,這樣就可能在沒有把整個文件裝入內存時統計字數。爲解決此問題,你必須使兩個線程同步工做。

  • 所謂同步,是指散步在不一樣進程之間的若干程序片段,它們的運行必須嚴格按照規定的某種前後次序來運行,這種前後次序依賴於要完成的特定的任務。若是用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數狀況),經過其它機制實現訪問者對資源的有序訪問。在大多數狀況下,同步已經實現了互斥,特別是全部寫入資源的狀況一定是互斥的。少數狀況是指能夠容許多個訪問者同時訪問資源。

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

接下來進入正題

Python的Threading模塊

  • Threading用於提供線程相關的操做
#示例
#!/usr/bin/env python
# -*- coding:utf-8 -*-
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'

上述代碼建立了10個「前臺」線程,而後控制器就交給了CPU,CPU根據指定算法進行調度,分片執行指令。

更多方法:
  • 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 )

<線程鎖與gil的區別> :

  • GIL是防止C語言的原生線程執行時互相沖掉數據,因數據是共享的;
  • 線程鎖是防止python代碼執行時(在操做系統之上)互相沖掉數據;
  • 所以在多線程中若是要同時修改同一數據,就要加鎖
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()方法返回假

相關文章
相關標籤/搜索