(三十三)線程

1、線程python

是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。一個線程能夠建立和撤消另外一個線程,同一進程中的多個線程之間能夠併發執行。線程有就緒、阻塞和運行三種基本狀態。安全

2、線程的兩種建立方式併發

1.第一種方式app

from  threading  import  Thread
  T = Thread(function,args=(arg1,arg2,...))
T.start()

  

2.第二種方式編輯器

from  threading  import  Thread
  Class MThread(Thread):
      Def run(self):
Pass
mt = MThread()
mt.start()

  

3、線程的空間ui

  1. 查看pid
import os
def f():
    print(os.getpid())
t = Thread(target=f,)
t.start()

  

2.線程空間不是隔離的spa

 

import os
def f():
    print('子線程pid:', os.getpid())#(1)
t = Thread(target=f,)
t.start()
print('主線程pid:', os.getpid())#(2)
說明:(1)、(2)兩處的值是同樣的。可見,線程都是在一個進程內,而一個進程都有本身獨立的空間。

 

  

3.線程與進程的效率對比操作系統

1)只是建立線程和進程線程

 

def f1():
    pass
def f2():
    pass
if __name__ == '__main__':
    t_s = time.time()
    t_lst = []
    for i in range(20):
        t = Thread(target=f1,)
        t.start()
        t_lst.append(t)

    for tt in t_lst:
        tt.join()
    t_e = time.time()

    p_s = time.time()
    p_lst = []
    for i in range(20):
        p = Process(target=f2,)
        p.start()
        p_lst.append(p)

    for pp in p_lst:
        pp.join()
    p_e = time.time()

    print('線程建立時間:', t_e - t_s)
    print('進程建立時間:', p_e - p_s)

結果:
線程建立時間: 0.004002809524536133
進程建立時間: 1.838303565979004

 

  

經過結果能夠看出來,一樣建立20個,線程只須要了0.004秒,也就是4毫秒,而建立進程倒是1838.3毫秒。得出一個結論:進程建立過程比線程建立過程要麻煩。由於進程建立的過程是,須要在內存裏開闢一個空間,把解釋器代碼加載進來,還須要把本身寫的程序也加載進去。而線程是進程裏的一個實體,只須要進程中的一點資源。全部線程共享進程中的全部資源。blog

 

(2)再來一個線程中有I/O阻塞的比較

 

(2)def f1():
    print('f1>>>>>aaaaa')
    time.sleep(1)
    print("f1>>>>>bbbbb")
def f2():
    print('f2>>>>>aaaaa')
    time.sleep(1)
    print("f2>>>>>bbbbb")
if __name__ == '__main__':
    t_s = time.time()
    t_lst = []
    for i in range(20):
        t = Thread(target=f1,)
        t.start()
        t_lst.append(t)

    for tt in t_lst:
        tt.join()
    t_e = time.time()

    p_s = time.time()
    p_lst = []
    for i in range(20):
        p = Process(target=f2,)
        p.start()
        p_lst.append(p)

    for pp in p_lst:
        pp.join()
    p_e = time.time()

    print('線程建立時間:', t_e - t_s)
    print('進程建立時間:', p_e - p_s)
結果:
。。。。。
線程建立時間: 1.0074326992034912
進程建立時間: 3.006932497024536

 

  

線程只是在一個進程中操做,這樣就利用多道技術,實現了併發(Python中的線程不能實現多核方式,後面介紹)。進程卻要建立20個,開銷,時間都要比線程的多。

(3)這個是計算型的操做

def f1():
    # print('f1>>>>>aaaaa')
    # time.sleep(1)
    # print("f1>>>>>bbbbb")
    n = 10
    for i in range(10000000):
        n += i
def f2():
    # print('f2>>>>>aaaaa')
    # time.sleep(1)
    # print("f2>>>>>bbbbb")
    n = 10
    for i in range(10000000):
        n += i
if __name__ == '__main__':
    t_s = time.time()
    t_lst = []
    for i in range(5):
        t = Thread(target=f1,)
        t.start()
        t_lst.append(t)

    for tt in t_lst:
        tt.join()
    t_e = time.time()

    p_s = time.time()
    p_lst = []
    for i in range(5):
        p = Process(target=f2,)
        p.start()
        p_lst.append(p)

    for pp in p_lst:
        pp.join()
    p_e = time.time()

    print('線程操做時間:', t_e - t_s)
    print('進程操做時間:', p_e - p_s)
結果:
線程操做時間: 3.7418324947357178
進程操做時間: 2.925071954727173

  

 

好神奇啊,此次進程使用的時間短了,線程的卻多了。這也是由於,線程不能使用多核技術。

 

4、鎖

  1. Lock

   先看第一段代碼:

 

num = 100
def f():
    global num
    tmp = num
    tmp -= 1
    time.sleep(0.01)
    num = tmp

if __name__ == '__main__':
    t_lst = []
    for i in range(10):
        t1 = Thread(target=f)
        t1.start()
        t_lst.append(t1)
    [t.join() for t in t_lst]
    print(num)

執行結果:
99

 

  

上面這段代碼的做用是,想循環10次,爲num每次減1,可是結果倒是99。緣由就是,線程執行的太快了,致使10個線程都執行到了time.sleep0.01)這裏,而後都去等着操做系統再次調用。調用到了後,再次執行賦值的操做,這樣num就是99了。

爲了數據安全,加把鎖吧,看代碼:

num = 100
def f(loc):
    global num
    loc.acquire()
    tmp = num
    tmp -= 1
    time.sleep(0.01)
    num = tmp
    loc.release()

if __name__ == '__main__':
    loc = Lock()
    t_lst = []
    for i in range(10):
        t1 = Thread(target=f,args=(loc,))
        t1.start()
        t_lst.append(t1)
    [t.join() for t in t_lst]
    print(num)
結果:
90

  

此次完成了心願,結果是90了。當第一個線程拿到鎖後,執行全部的操做,即使有sleep須要等待,其餘9個線程也得等着,必須等着第一完成了。第一個完成後,接下來就是第二個線程,也跟第一個同樣,無論其餘的怎麼着急,就是慢慢執行本身的。依次類推,最後結果就是90了。

2.死鎖

在工做中有可能會有鎖的嵌套,稍有不慎,那麼就會死鎖了。仍是看代碼:

# def f1(locA, locB):
#     locA.acquire()
#     print('f1aaaaaaaaaaaa')
#     time.sleep(0.1)
#     locB.acquire()
#     print('f1bbbbbbbbb')
#     locB.release()
#     locA.release()
# def f2(locA, locB):
#     locB.acquire()
#     print('f2--aaaaaaaa')
#     time.sleep(0.1)
#     locA.acquire()
#     print('f2--bbbbbbbb')
#     locA.release()
#     locB.release()
# if __name__ == '__main__':
#     locA = Lock()
#     locB = Lock()
#
#     t1 = Thread(target=f1, args=(locA, locB))
#     t2 = Thread(target=f2, args=(locA, locB))
#
#     t1.start()
#     t2.start()

  

運行後,會看到程序一直處於運行中。不會往下走了,由於線程t1等着要locB的鎖,而線程t2等着要線程locA的鎖,從而致使兩邊就這樣互相等待着,程序一直不運行。

 

3.遞歸鎖

爲了解決死鎖,Python出現了遞歸鎖。看代碼:

 

# def f1(locA, locB):
#     locA.acquire()
#     print('f1aaaaaaaaaaaa')
#     time.sleep(0.1)
#     locB.acquire()
#     print('f1bbbbbbbbb')
#     locB.release()
#     locA.release()
# def f2(locA, locB):
#     # locB.acquire()
#     with locB:
#         print('f2--aaaaaaaa')
#         time.sleep(0.1)
#         # locA.acquire()
#         with locA:
#             print('f2--bbbbbbbb')
#         # locA.release()
#     # locB.release()
# if __name__ == '__main__':
#     locA = locB = RLock()
#     t1 = Thread(target=f1, args=(locA, locB))
#     t2 = Thread(target=f2, args=(locA, locB))
#
#     t1.start()
#     t2.start()

 

  

遞歸鎖,當acquire時,內部會有計數器,加1;前面acquire幾回,就會記爲幾,當釋放時,會依次再把鎖釋放掉。

 

4.GIL

這個是加載cpython解釋器上的一把鎖,由於它而致使Python的線程不能使用多核技術,只能串行。看圖:

                      

 

接下來看圖說話:當咱們運行了一個py文件後,其實就是啓動了一個進程,操做系統就會把代碼讀取到內容中,併爲這個進程分配相應的內存空間。在這個進程中,還會讀入解釋器的代碼。編輯器會把這些代碼處理成C語言的字節碼,而後虛擬機把這些字節碼再處理成二進制,這樣CPU就能夠處理了。

GIL鎖就是加在解釋器上的,每次只能有一個線程拿到這個GIL鎖,其餘的線程只能等待前面的把鎖釋放了再拿。遇到I/O阻塞的,操做系統,會把GIL鎖拿回來,交給下一個線程。若是再遇到I/O阻塞還會繼續拿過來交給下一個線程。這樣就實現了相似單核的併發。

還有一些計算型的程序,使用線程時,第一個線程拿到了GIL鎖後,會一直執行完畢。而後,操做系統把鎖再交給下一個線程,這個線程仍是從頭執行到尾。也就是遇到計算型,中間沒有I/O阻塞的程序,就是串行,一個一個線程去執行。

這裏,就能夠看出前面進程和線程比較時,關於時間多少的問題了。

相關文章
相關標籤/搜索