Python学习并发与多线程
1、并發
1.1、并發與并行
并行,parallel,同一時刻,執行不同任務,并且相互沒有干擾;
并發,concurrency,一段時間內,交替執行不同的任務;
串行,一個任務執行完成后執行下一個任務;
1.2、并發的解決方法
“高并發模型”:例如早高峰的北京地鐵,在同一時刻,需要處理大量任務,可以理解為高并發模型;
解決方法:
(1)隊列,緩沖區:將任務排隊,形成隊列,先進先出,就解決了資源的使用問題;形成的隊列其實就是一個緩沖區域,假設排隊有一種優先機制,例如女士優先,則次隊列為優先對列;
(2)爭搶:誰搶到資源就上鎖,排他性的鎖,其他任務只能等待;爭搶是一種高并發的解決方案,但是這樣不好,因為可能有任務長期霸占資源,有人一直搶不到資源;
(3)預處理:將各個任務會用到的熱點數據提前緩存,這樣就減少了任務的執行時間;
(4)并行:開辟多的資源,例如銀行辦理業務,排隊的人多了,可以多增加業務窗口;此方案為水平擴展的思想;
(5)提速:簡單的就是提高資源性能,提高cpu性能或者單個服務器安裝更多的cpu,此方案為垂直擴展
(6)消息中間件:模型可以理解為北京地鐵站外面的排隊走廊,具有緩沖人流量的功能;常見的消息隊列有:RabbitMQ、ActiveMQ、RocketMQ(阿里)、kafka等
2、進程與線程
# 進程與線程的關系:
程序是源碼編譯后的文件,而這些文件被存放在磁盤上;當程序被操作系統加載到內存中,就是進程,進程中存放著指令和數據(資源),進程是線程的容器;線程是操作系統進行運算調度的最小單位;
2.1、線程的狀態
2.2、python中的進程與線程
# 進程會啟動一個解釋器進程,線程共享一個解釋器進程;會有一個GIL,全局解釋器鎖,來分配具體線程任務執行;python的GIL保證同一時刻只有一個線程被執行;
3、Python的多線程開發
3.1、Thread類
# Python的線程開發使用標準款threading;
# Thread類的初始化方法
def __init__(self, group=None, target=None, name=None,args=(), kwargs=None, *, daemon=None):參數:
target 線程調用的對象,就是目標函數;
name 為線程起名
args 為目標函數傳遞位置參數,元祖;
kwargs 為目標函數傳遞關鍵字參數,字典
(1)線程的啟動:
通過threading.Thread創建一個線程對象,target是目標函數,name指定線程名稱,到此線程被創建完成,需要調用start() 方法來啟動線程 ;
import threadingdef work():print('I m working')print('Finished')t=threading.Thread(target=work,name='workerthread') # 創建線程t t.start() # 啟動線程t
(2)線程的退出:
# python沒有提供線程的退出方法,但是線程在以下情況會退出:
①:線程函數內語句執行完成;
②:線程函數中拋出未處理異常;
# python的線程沒有優先級、沒有線程組的概念,也不能被銷毀,停止,掛起,也沒有恢復和中斷;
(3)線程的傳參
# 線程的傳參本質上和函數沒有區別
import threadingdef add(x,y):print(x+y)t=threading.Thread(target=add,args=(4,5),name='workerthread') # args=(4,5)傳遞位置參數; t.start()(4)threading的屬性和方法
# current_thread()? 返回當前線程對象
# main_thread() 返回主線程對象
# active_count() 返回處于alive狀態的線程數
# enumerate() 返回所有活著的線程列表,不包括已經終止的線程和未開始的線程
# get_ident() 返回當前線程的id,非0整數
(5)Thread實例的屬性和方法
# name:線程名稱
# ident:線程id,線程啟動后才有id,否則為None
# is_alive():是否存活
# start()方法與run()方法
①:start() 啟動線程,每一個線程必須并且只能執行一次該方法;
def start(self):if not self._initialized:raise RuntimeError("thread.__init__() not called")if self._started.is_set():raise RuntimeError("threads can only be started once")with _active_limbo_lock:_limbo[self] = selftry:_start_new_thread(self._bootstrap, ()) # 啟動一個線程except Exception:with _active_limbo_lock:del _limbo[self]raiseself._started.wait()②:run() 并不啟動新線程,只是在主線程中調用了一個函數,調用定義線程對象中的target函數;
def run(self):try:if self._target:self._target(*self._args, **self._kwargs) # 執行target函數finally:# Avoid a refcycle if the thread is running a function with# an argument that has a member that points to the thread.del self._target, self._args, self._kwargs?
3.2、多線程
#? 多線程,一個進程中如果有多個線程,就是多線程,實現一種并發;
# 一個進程中至少有一個線程,并作為程序的入口,這個線程就是主線程;一個進程至少有一個主線程,其他線程為工作線程;
# 線程安全與線程不安全
def worker():for x in range(100):print('{} is runing'.format(threading.current_thread().name))for x in range(1,5):name='work-{}'.format(x)t= threading.Thread(target=worker,name=name)t.start()運行結果: work-1 is runing work-2 is runingwork-1 is runingwork-3 is runingwork-4 is runing work-3 is runingwork-2 is runingwork-1 is runing分析: 看代碼,應該是一行行打印,但是很多字符串打印在了一起? 說明print函數被打斷了,被線程切換打斷了,print函數分2步,第一步打印字符串,第二步打印換行,就在2步之間被打斷,發生了線程切換 每個線程是根據系統調用cpu分時計算的,可能出現線程1的函數還沒有執行完成,則cpu資源被線程2給搶占,則去執行線程2的函數; 解決方法:字符串是不可變數據類型,它作為一個整體不可分割輸出,end=''不再讓print換行,但沒有解決根本問題,應為print函數是線程不安全3.3、daemon線程與non-daemon線程
# 注意:daemon不是linux中的守護進程,daemon屬性必須在start方法之前設置好;
在主線程運行結束后會檢查是否含有,沒有執行完成的non-daemon線程,
如果有則等待non-daemon線程運行完成;
沒有則直接結束線程;
# 總結:
①:線程具有一個daemon屬性,可以顯示設置為True或False,也可以不設置,則默認為None;
②:如果不設置daemon,就取當前線程的daemon來設置它;
③:主線程是non-daemon,即daemon=False;
④:從主線程創建的所有線程不設置daemon,則默認daemon=False,也就是non-daemon線程;
⑤:Python程序在沒有活的non-daemon線程運行時退出,也就是剩下的只能是daemon線程,主線程才能退出,否則主線程只能等待;
3.4、join方法
import time import threadingdef foo(n):for i in range(n):print(i)time.sleep(1)t1=threading.Thread(target=foo,args=(10,),daemon=True) t1.start() t1.join() # 表示主線程,必須等到t1線程結束以后才結束; print('main thread end')''' 運行結果: 0 1 2 . . 9 main thread end分析:由于t1.join()方法后,將主線程阻塞,等待t1線程結束; '''# join(timeout=None),是線程的標準方法之一;
# 一個線程中調用另外一個線程的join方法,調用著將被阻塞,直到被調用線程終止;
# 一個線程可以被join多次,timout參數指定調用則等待多久,沒有設置超時時間,就一直等待到被調用者線程終止;設置超時時間如果被調線程沒有結束,則調用者不等待,直接結束線程;
# 調用誰的join方法,就是join誰,就要等待誰;
4、threading.local類
# python提供的threading.local類,將這個類實例化得到一個全局對象,但是不同的線程使用這個對象存儲數據其他線程看不見;
# 本質:threading.local類構建了一個大字典,存放所有線程相關的字典,定義如下: { id(Thread) -> (ref(Thread), thread-local dict) }
# 每一個線程的id為key,元祖為value;
# value中2部分為:線程對象引用,每個線程自己的字典;
from threading import local import threading import timea=local()def worker():a.x=0for i in range(10):# print(a.__dict__,current_thread().ident)time.sleep(0.0001)a.x+=1print(threading.current_thread(),a.x,a.__dict__)for i in range(5):threading.Thread(target=worker).start()''' 運行結構: <Thread(Thread-3, started 14488)> 10 {'x': 10} <Thread(Thread-4, started 14420)> 10 {'x': 10} <Thread(Thread-5, started 13620)> 10 {'x': 10} <Thread(Thread-1, started 11808)> 10 {'x': 10} <Thread(Thread-2, started 15348)> 10 {'x': 10} '''5、定時器Timer
# Timer是線程Thread的子類,就是線程類,具有線程的屬性和方法;
# 它的實例就是能延時執行目標函數的線程,在真正執行目標函數之前,都可以cancel它;
# cancel方法本質使用Event類實現;
# 自定義實現Timer類:
class MyTimer:def __init__(self,interval,func,args=(),kwargs={}):self.interval=intervalself.func=funcself.args=argsself.kwargs=kwargsself.event=Event()def cancel(self):print("call cancel------>")self.event.set()def start(self):Thread(target=self._run).start() # 在這里起一個線程是為了避免在主線程被阻塞,執行cancel方法不生效的問題def _run(self):self.event.wait(self.interval)if not self.event.is_set():self.func(*self.args,**self.kwargs)# Thread(target=self.func,args=self.args,kwargs=self.kwargs).start() self.event.set()def add(x,y):print(3,'========>',x + y)t1=MyTimer(3,add,(4,5))t1.start() t1.cancel()print('Main Thread end')?
轉載于:https://www.cnblogs.com/soulgou123/p/9877593.html
總結
以上是生活随笔為你收集整理的Python学习并发与多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hadoop单机/伪分布式集群搭建(新手
- 下一篇: Python 操作 Excel,总有一个