python多線程詳細介紹

  python中的多線程是一個非常重要的知識點,今天爲大家對多線程進行詳細的說明,代碼中的註釋有多線程的知識點還有測試用的實例。

  import threading

  from threading import Lock,Thread

  import time,os

  '''

  python多線程詳解

  什麼是線程?

  線程也叫輕量級進程,是操作系統能夠進行運算調度的最小單位,它被包涵在進程之中,是進程中的實際運作單位。

  線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其他線程共享進程所

  擁有的全部資源。一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程之間可以併發執行

  '''

  '''

  爲什麼要使用多線程?

  線程在程序中是獨立的、併發的執行流。與分隔的進程相比,進程中線程之間的隔離程度要小,它們共享內存、文件句柄

  和其他進程應有的狀態。

  因爲線程的劃分尺度小於進程,使得多線程程序的併發性高。進程在執行過程之中擁有獨立的內存單元,而多個線程共享

  內存,從而極大的提升了程序的運行效率。

  線程比進程具有更高的性能,這是由於同一個進程中的線程都有共性,多個線程共享一個進程的虛擬空間。線程的共享環境

  包括進程代碼段、進程的共有數據等,利用這些共享的數據,線程之間很容易實現通信。

  操作系統在創建進程時,必須爲改進程分配獨立的內存空間,並分配大量的相關資源,但創建線程則簡單得多。因此,使用多線程

  來實現併發比使用多進程的性能高得要多。

  '''

  '''

  總結起來,使用多線程編程具有如下幾個優點:

  進程之間不能共享內存,但線程之間共享內存非常容易。

  操作系統在創建進程時,需要爲該進程重新分配系統資源,但創建線程的代價則小得多。因此使用多線程來實現多任務併發執行比使用多進程的效率高

  python語言內置了多線程功能支持,而不是單純地作爲底層操作系統的調度方式,從而簡化了python的多線程編程。

  '''

  '''

  普通創建方式

  '''

  # def run(n):

  # print('task',n)

  # time.sleep(1)

  # print('2s')

  # time.sleep(1)

  # print('1s')

  # time.sleep(1)

  # print('0s')

  # time.sleep(1)

  #

  # if __name__ == '__main__':

  # t1 = threading.Thread(target=run,args=('t1',)) # target是要執行的函數名(不是函數),args是函數對應的參數,以元組的形式存在

  # t2 = threading.Thread(target=run,args=('t2',))

  # t1.start()

  # t2.start()

  '''

  自定義線程:繼承threading.Thread來定義線程類,其本質是重構Thread類中的run方法

  '''

  # class MyThread(threading.Thread):

  # def __init__(self,n):

  # super(MyThread,self).__init__() #重構run函數必須寫

  # self.n = n

  #

  # def run(self):

  # print('task',self.n)

  # time.sleep(1)

  # print('2s')

  # time.sleep(1)

  # print('1s')

  # time.sleep(1)

  # print('0s')

  # time.sleep(1)

  #

  # if __name__ == '__main__':

  # t1 = MyThread('t1')

  # t2 = MyThread('t2')

  # t1.start()

  # t2.start()

  '''

  守護線程

  下面這個例子,這裏使用setDaemon(True)把所有的子線程都變成了主線程的守護線程,

  因此當主線程結束後,子線程也會隨之結束,所以當主線程結束後,整個程序就退出了。

  所謂’線程守護’,就是主線程不管該線程的執行情況,只要是其他子線程結束且主線程執行完畢,主線程都會關閉。也就是說:主線程不等待該守護線程的執行完再去關閉。

  '''

  # def run(n):

  # print('task',n)

  # time.sleep(1)

  # print('3s')

  # time.sleep(1)

  # print('2s')

  # time.sleep(1)

  # print('1s')

  #

  # if __name__ == '__main__':

  # t=threading.Thread(target=run,args=('t1',))

  # t.setDaemon(True)

  # t.start()

  # print('end')

  '''

  通過執行結果可以看出,設置守護線程之後,當主線程結束時,子線程也將立即結束,不再執行

  '''

  '''

  主線程等待子線程結束

  爲了讓守護線程執行結束之後,主線程再結束,我們可以使用join方法,讓主線程等待子線程執行

  '''

  # def run(n):

  # print('task',n)

  # time.sleep(2)

  # print('5s')

  # time.sleep(2)

  # print('3s')

  # time.sleep(2)

  # print('1s')

  # if __name__ == '__main__':

  # t=threading.Thread(target=run,args=('t1',))

  # t.setDaemon(True) #把子線程設置爲守護線程,必須在start()之前設置

  # t.start()

  # t.join() #設置主線程等待子線程結束

  # print('end')

  '''

  多線程共享全局變量

  線程時進程的執行單元,進程時系統分配資源的最小執行單位,所以在同一個進程中的多線程是共享資源的

  '''

  # g_num = 100

  # def work1():

  # global g_num

  # for i in range(3):

  # g_num+=1

  # print('in work1 g_num is : %d' % g_num)

  #

  # def work2():

  # global g_num

  # print('in work2 g_num is : %d' % g_num)

  #

  # if __name__ == '__main__':

  # t1 = threading.Thread(target=work1)

  # t1.start()

  # time.sleep(1)

  # t2=threading.Thread(target=work2)

  # t2.start()

  '''

  由於線程之間是進行隨機調度,並且每個線程可能只執行n條執行之後,當多個線程同時修改同一條數據時可能會出現髒數據,

  所以出現了線程鎖,即同一時刻允許一個線程執行操作。線程鎖用於鎖定資源,可以定義多個鎖,像下面的代碼,當需要獨佔

  某一個資源時,任何一個鎖都可以鎖定這個資源,就好比你用不同的鎖都可以把這個相同的門鎖住一樣。

  由於線程之間是進行隨機調度的,如果有多個線程同時操作一個對象,如果沒有很好地保護該對象,會造成程序結果的不可預期,

  我們因此也稱爲“線程不安全”。

  爲了防止上面情況的發生,就出現了互斥鎖(Lock)

  '''

  # def work():

  # global n

  # lock.acquire()

  # temp = n

  # time.sleep(0.1)

  # n = temp-1

  # lock.release()

  #

  #

  # if __name__ == '__main__':

  # lock = Lock()

  # n = 100

  # l = []

  # for i in range(100):

  # p = Thread(target=work)

  # l.append(p)

  # p.start()

  # for p in l:

  # p.join()

  '''

  遞歸鎖:RLcok類的用法和Lock類一模一樣,但它支持嵌套,在多個鎖沒有釋放的時候一般會使用RLock類

  '''

  # def func(lock):

  # global gl_num

  # lock.acquire()

  # gl_num += 1

  # time.sleep(1)

  # print(gl_num)

  # lock.release()

  #

  #

  # if __name__ == '__main__':

  # gl_num = 0

  # lock = threading.RLock()

  # for i in range(10):

  # t = threading.Thread(target=func,args=(lock,))

  # t.start()

  '''

  信號量(BoundedSemaphore類)

  互斥鎖同時只允許一個線程更改數據,而Semaphore是同時允許一定數量的線程更改數據,比如廁所有3個坑,

  那最多隻允許3個人上廁所,後面的人只能等裏面有人出來了才能再進去

  '''

  # def run(n,semaphore):

  # semaphore.acquire() #加鎖

  # time.sleep(3)

  # print('run the thread:%s\n' % n)

  # semaphore.release() #釋放

  #

  #

  # if __name__== '__main__':

  # num=0鄭州人流醫院哪家好 http://www.zzzy120.com/

  # semaphore = threading.BoundedSemaphore(5) #最多允許5個線程同時運行

  # for i in range(22):

  # t = threading.Thread(target=run,args=('t-%s' % i,semaphore))

  # t.start()

  # while threading.active_count() !=1:

  # pass

  # else:

  # print('----------all threads done-----------')

  '''

  python線程的事件用於主線程控制其他線程的執行,事件是一個簡單的線程同步對象,其主要提供以下的幾個方法:

  clear將flag設置爲 False

  set將flag設置爲 True

  is_set判斷是否設置了flag

  wait會一直監聽flag,如果沒有檢測到flag就一直處於阻塞狀態

  事件處理的機制:全局定義了一個Flag,當Flag的值爲False,那麼event.wait()就會阻塞,當flag值爲True,

  那麼event.wait()便不再阻塞

  '''

  event = threading.Event()

  def lighter():

  count = 0

  event.set() #初始者爲綠燈

  while True:

  if 5 < count <=10:

  event.clear() #紅色燈,清除標誌位

  print("\33[41;lmred light is on...\033[0m]")

  elif count > 10:

  event.set() #綠燈,設置標誌位

  count = 0

  else:

  print('\33[42;lmgreen light is on...\033[0m')

  time.sleep(1)

  count += 1

  def car(name):

  while True:

  if event.is_set(): #判斷是否設置了標誌位

  print('[%s] running.....'%name)

  time.sleep(1)

  else:

  print('[%s] sees red light,waiting...'%name)

  event.wait()

  print('[%s] green light is on,start going...'%name)

  # startTime = time.time()

  light = threading.Thread(target=lighter,)

  light.start()

  car = threading.Thread(target=car,args=('MINT',))

  car.start()

  endTime = time.time()

  # print('用時:',endTime-startTime)

  '''

  GIL 全局解釋器

  在非python環境中,單核情況下,同時只能有一個任務執行。多核時可以支持多個線程同時執行。但是在python中,無論有多少個核

  同時只能執行一個線程。究其原因,這就是由於GIL的存在導致的。

  GIL的全程是全局解釋器,來源是python設計之初的考慮,爲了數據安全所做的決定。某個線程想要執行,必須先拿到GIL,我們可以

  把GIL看做是“通行證”,並且在一個python進程之中,GIL只有一個。拿不到線程的通行證,並且在一個python進程中,GIL只有一個,

  拿不到通行證的線程,就不允許進入CPU執行。GIL只在cpython中才有,因爲cpython調用的是c語言的原生線程,所以他不能直接操

  作cpu,而只能利用GIL保證同一時間只能有一個線程拿到數據。而在pypy和jpython中是沒有GIL的

  python在使用多線程的時候,調用的是c語言的原生過程。

  '''

  '''

  python針對不同類型的代碼執行效率也是不同的

  1、CPU密集型代碼(各種循環處理、計算等),在這種情況下,由於計算工作多,ticks技術很快就會達到閥值,然後出發GIL的

  釋放與再競爭(多個線程來回切換當然是需要消耗資源的),所以python下的多線程對CPU密集型代碼並不友好。

  2、IO密集型代碼(文件處理、網絡爬蟲等設計文件讀寫操作),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,

  造成不必要的時間浪費,而開啓多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序的執行

  效率)。所以python的多線程對IO密集型代碼比較友好。

  '''

  '''

  主要要看任務的類型,我們把任務分爲I/O密集型和計算密集型,而多線程在切換中又分爲I/O切換和時間切換。如果任務屬於是I/O密集型,

  若不採用多線程,我們在進行I/O操作時,勢必要等待前面一個I/O任務完成後面的I/O任務才能進行,在這個等待的過程中,CPU處於等待

  狀態,這時如果採用多線程的話,剛好可以切換到進行另一個I/O任務。這樣就剛好可以充分利用CPU避免CPU處於閒置狀態,提高效率。但是

  如果多線程任務都是計算型,CPU會一直在進行工作,直到一定的時間後採取多線程時間切換的方式進行切換線程,此時CPU一直處於工作狀態,

  此種情況下並不能提高性能,相反在切換多線程任務時,可能還會造成時間和資源的浪費,導致效能下降。這就是造成上面兩種多線程結果不能的解釋。

  結論:I/O密集型任務,建議採取多線程,還可以採用多進程+協程的方式(例如:爬蟲多采用多線程處理爬取的數據);對於計算密集型任務,python此時就不適用了。

  '''


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章