線程
1.什么是線程
-
進程是一個執行空間 , 線程就是其中真正工作的單位 , 每一個進程至少有一個線程(如果我們把操作系統比喻為一個工廠 , 進程就是車間 , 線程就是流水線)
進程包含了運行該程序所需要所有資源 , 進程是一個資源單位 , 線程是CPU的最小執行單位
每一個進程一旦被創建 , 就默認開啟了一條線程 , 稱之為主線程
2.為什么使用線程
-
多線程指的是,在一個進程中開啟多個線程,簡單的講:如果多個任務共用一塊地址空間,那么必須在一個進程內開啟多個線程。詳細的講分為4點:
-
多線程共享一個進程的地址空間
-
線程比進程更輕量級,線程比進程更容易創建可撤銷,在許多操作系統中,創建一個線程比創建一個進程要快10-100倍,在有大量線程需要動態和快速修改時,這一特性很有用
-
若多個線程都是CPU密集型的,那么并不能獲得性能上的增強,但是如果存在大量的計算和大量的I/O處理,擁有多個線程允許這些活動彼此重疊運行,從而會加快程序執行的速度。
-
在多CPU系統中,為了最大限度的利用多核,可以開啟多個線程,比開進程開銷要小的多。(這一條并不適用于python)
-
使用線程可以提高程序效率
為何不用多進程提高效率 : 是因為進程對操作系統的資源耗費非常高
3.線程與進程的區別:
-
線程共享創建它的進程的地址空間 ; 進程有自己的地址空間。
-
線程可以直接訪問其進程的數據段;進程擁有自己父進程數據段的副本。
-
線程可以直接與其進程的其他線程通信;進程必須使用進程間通信來與兄弟進程通信。
-
新線程很容易創建;新流程需要復制父流程。線程可以對同一進程的線程進行相當大的控制;進程只能控制子進程。對主線程的更改(取消,優先級更改等)可能會影響進程的其他線程的行為;對父進程的更改不會影響子進程。
4.什么時候開啟多線程
-
當程序中遇到IO操作時(當程序中時純計算任務時 也無法提高效率)
5.如何使用
-
from threading import Thread
創建線程與創建進程的方式幾乎一樣 , 但是創建子進程會將父進程的資源復制執行一遍 , 所以必須在__main__
下執行 , 而創建線程則不一樣 , 線程間共享進程資源,所以不需要復制執行父線程代碼,所以可以不加__main__
。
一 . 創建線程的兩種方式
-
調用類型
from threading import Thread ? ? def task():print('thread running...') ? ? if __name__ == '__main__':t = Thread(target=task)t.start() ?print('主線程') ? # 執行結果 # thread running... (子線程比主線程執行速度更快) # 主線程
-
繼承類型
from threading import Thread ? ? class Sayhi(Thread):def __init__(self,name):super().__init__()self.name=namedef run(self):print('%s say hello' % self.name) ? ? if __name__ == '__main__':t = Sayhi('jason')t.start()print('主線程')# 執行結果 #jason say hello #主線程
?
二 . 線程的常用方法
Thread實例化對象的方法: # 1.isAlive(): 判斷線程是否還存在(未終止) # 2.getName(): 返回線程名 # 3.setName(): 設置線程名 ? threading模塊下提供的方法 # threading.currentThread(): 返回當前的線程變量 # threading.enumerate(): 返回一個正在運行的線程列表 # threading.activecount(): 返回當前運行的線程數量 # len(threading.activecount()): 與上一個方法返回相同值
?
三 . 在一個進程下開啟多個線程與在一個進程下開啟多個子進程的區別
-
開啟速度 : 開啟線程>>開啟進程(所以程序效率會大大提高)
-
id :
-
進程開啟多線程的pid都相同 ( 很好理解pid是process id , 所以線程pid相同)
-
進程開啟多個進程的pid不同
-
-
空間資源:
-
進程開啟的多線程的共享進程內的資源 , 某線程修改數據后 . 其他線程再訪問則是新的數據
-
進程開啟多個子進程的數據互相獨立 , 子進程內修改數據不會對其他進程數據造成干擾
-
?
守護線程
守護線程(setdaemon)
-
守護線程 : 守護線程會在所有非守護線程結束后結束
守護線程本質是上是在守護主線程 ,但是對于主線程來說 , 運行完畢指的是主線程所在的進程內所有非守護進程統統運行完畢,主線程才算運行完畢
from threading import Thread import time ? def task1():print('sub thread is running...')time.sleep(0.5)print('sub thread end...') def task2():time.sleep(0.1)print('task2 is run...') ? t1 = Thread(target=task1) t2 = Thread(target= task2) ? t1.setDaemon(True) # 將t1設置為守護線程, 必須在start之前設置 ? t1.start() t2.strat() ? # 執行結果 #sub thread run... #task2 is run... ? ?
?
線程互斥鎖(Lock)
-
什么時候用鎖 :
當多個進程或多個線程需要同時修改同一份數據時,可能會造成數據錯亂,所以必須加鎖
import time from threading import Thread,Lock ? lock =Lock() # 實例化鎖對象 ? a = 100 ? def task():lock.acquire() # 給線程上鎖global a # 訪問全局atemp = a - 1 # 修改全局atime.sleep(0.01)a = templock.release() # 釋放鎖,線程執行完畢 ? ts = [] for i in range(100):t = Thread(target=task)t.start()ts.append(t) ? for t in ts: # lock保證了多線程串行,同時主線程print(a)也在其中,但是我們想得到最終結果,所以用join人為設置順序 t.join() ? ? print(a)
信號量(Semaphore)
信號量:
-
其實也是一種鎖 , 特點是可以設置一個數據可以被幾個線程(進程)共享.
與普通鎖的區別:
-
普通鎖一旦加鎖,則意味著這個數據在同一時間只能被一個線程使用
-
信號量這種鎖,特點是可以設置一個數據可以被幾個線程(進程)共享
-
-
使用場景
-
可以限制一個數據同時訪問的次數 , 保證程序正常運行
-
from threading import Thread,Semaphore import time sem = Semaphore(3) # 設置最大訪問進程數 ? ? def task():sem.acquire()print('你好啊')time.sleep(3)sem.release() ? ? for i in range(10):t = Thread(target=task) ?t.start()# 執行結果太長, 就不打印了 # 現象描述 : 就是3個一次打印
守護進程的使用
""" 用生產者消費者模型實現一個顧客吃漢堡的功能 主要是生產者生產處漢堡放入隊列,然后消費者吃掉, 要判斷什么時候顧客吃完了所有生產了的漢堡 """ from multiprocessing import Process, JoinableQueue import time, random ? def eat_hotdog(name, q):while True:res = q.get()print('%s吃了%s' % (name,res))time.sleep(random.randint(1,2))q.task_done() ? ? def make_Hotdog(name, q):for i in range(1,6):time.sleep(random.randint(1,2))print('%s生產了第%s個熱狗' % (name, i))res = '%s的%s個熱狗' % (name,i)q.put(res) ? ? if __name__ == '__main__':q = JoinableQueue() ?#生產者c1 = Process(target=make_Hotdog, args=('a熱狗店', q))c1.start() ?#生產者2c2 = Process(target=make_Hotdog, args=('b熱狗店',q))c2.start() ?# 消費者p2 = Process(target=eat_hotdog, args=('顧客',q))p2.daemon = True # 隊列阻塞打開,主進程執行完畢,守護進程死 p2.start() ? ?# 保證生產者全部完成 c1.join()c2.join() ? ?# 保證隊列中的數據全部被處理了'''join:阻止,直到隊列中的所有項目都已獲取并處理完畢。 ?每當項目添加到隊列時,未完成任務的計數就會增加。 每當消費者調用task_done()以指示該項目已被檢索并且其上的所有工作都已完成時,計數就會下降。 當未完成任務的數量降至零時,join()取消阻塞。'''q.join() # JoinableQueue方法
?