python多線程詳解
一、多線程介紹
1、什么是線程?
線程也叫輕量級進程,是操作系統能夠進行運算調度的最小單位,它被包涵在進程之中,是進程中的實際運作單位。
線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其他線程共享進程所擁有的全部資源。
一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程之間可以并發執行。
2、為什么要使用多線程?
線程在程序中是獨立的、并發的執行流。與分隔的進程相比,進程中線程之間的隔離程度要小,它們共享內存、文件句柄和其他進程應有的狀態。
因為線程的劃分尺度小于進程,使得多線程程序的并發性高。進程在執行過程之中擁有獨立的內存單元,而多個線程共享內存,從而極大的提升了程序的運行效率。
線程比進程具有更高的性能,這是由于同一個進程中的線程都有共性,多個線程共享一個進程的虛擬空間。線程的共享環境 包括進程代碼段、進程的共有數據等,利用這些共享的數據,線程之間很容易實現通信。
操作系統在創建進程時,必須為改進程分配獨立的內存空間,并分配大量的相關資源,但創建線程則簡單得多。因此,使用多線程來實現并發比使用多進程的性能高得要多。
3、總結起來,使用多線程編程具有如下幾個優點:
進程之間不能共享內存,但線程之間共享內存非常容易。
操作系統在創建進程時,需要為該進程重新分配系統資源,但創建線程的代價則小得多。因此使用多線程來實現多任務并發執行比使用多進程的效率高。
Python語言內置了多線程功能支持,而不是單純地作為底層操作系統的調度方式,從而簡化了Python的多線程編程。
二、代碼舉例
import threading
import time,os
'''
1、普通創建方式
'''
# 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__':
# target是要執行的函數名(不是函數),args是函數對應的參數,以元組的形式存在
# ? ? t1 = threading.Thread(target=run,args=('t1',))
# ? ? t2 = threading.Thread(target=run,args=('t2',))
# ? ? t1.start()
# ? ? t2.start()
'''
2、自定義線程:繼承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()
'''
3、守護線程
下面這個例子,這里使用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')
'''
通過執行結果可以看出,設置守護線程之后,當主線程結束時,子線程也將立即結束,不再執行
'''
'''
4、主線程等待子線程結束
為了讓守護線程執行結束之后,主線程再結束,我們可以使用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')
'''
5、多線程共享全局變量
線程是進程的執行單元,進程是系統分配資源的最小執行單位,所以在同一個進程中的多線程是共享資源的。
'''
# 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()
'''
6、互斥鎖(Lock)
由于線程之間是進行隨機調度,當多個線程同時修改同一條數據時可能會出現臟數據,
所以出現了線程鎖,即同一時刻只允許一個線程執行某些操作。
線程鎖用于鎖定資源,可以定義多個鎖,像下面的代碼,當需要獨占某一個資源時,
任何一個鎖都可以鎖定這個資源,就好比你用不同的鎖都可以把這個相同的門鎖住一樣。
由于線程之間是進行隨機調度的,如果有多個線程同時操作一個對象,
如果沒有很好地保護該對象,會造成程序結果的不可預期,也稱為“線程不安全”。
為了防止上面情況的發生,就出現了互斥鎖(Lock)
'''
# def work():
# ? ? global n
# ? ? lock.acquire()
# ? ? temp = n
# ? ? time.sleep(0.1)
# ? ? n = temp-1
# ? ? lock.release()
#
#
# if __name__ == '__main__':
# ? ? lock = threading.Lock()
# ? ? n = 100
# ? ? l = []
# ? ? for i in range(100):
# ? ? ? ? p = Thread(target=work)
# ? ? ? ? l.append(p)
# ? ? ? ? p.start()
# ? ? for p in l:
# ? ? ? ? p.join()
'''
7、遞歸鎖:RLcok類的用法和Lock類一模一樣,但它支持嵌套。
RLock類代表可重入鎖(Reentrant Lock)。
對于可重入鎖,在同一個線程中可以對它進行多次鎖定,
也可以多次釋放。如果使用 RLock,那么 acquire() 和 release() 方法必須成對出現。
如果調用了 n 次 acquire() 加鎖,則必須調用 n 次 release() 才能釋放鎖。
由此可見,RLock 鎖具有可重入性。也就是說,同一個線程可以對已被加鎖的 RLock 鎖再次加鎖,
RLock 對象會維持一個計數器來追蹤 acquire() 方法的嵌套調用,
線程在每次調用 acquire() 加鎖后,都必須顯式調用 release() 方法來釋放鎖。
所以,一段被鎖保護的方法可以調用另一個被相同鎖保護的方法。
'''
# 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()
'''
8、信號量(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
# ? ? 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-----------')
'''
9、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此時就不適用了。
'''
Python 任務調度 多線程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。