Python学习并发与多线程
1、并發(fā)
1.1、并發(fā)與并行
并行,parallel,同一時刻,執(zhí)行不同任務(wù),并且相互沒有干擾;
并發(fā),concurrency,一段時間內(nèi),交替執(zhí)行不同的任務(wù);
串行,一個任務(wù)執(zhí)行完成后執(zhí)行下一個任務(wù);
1.2、并發(fā)的解決方法
“高并發(fā)模型”:例如早高峰的北京地鐵,在同一時刻,需要處理大量任務(wù),可以理解為高并發(fā)模型;
解決方法:
(1)隊(duì)列,緩沖區(qū):將任務(wù)排隊(duì),形成隊(duì)列,先進(jìn)先出,就解決了資源的使用問題;形成的隊(duì)列其實(shí)就是一個緩沖區(qū)域,假設(shè)排隊(duì)有一種優(yōu)先機(jī)制,例如女士優(yōu)先,則次隊(duì)列為優(yōu)先對列;
(2)爭搶:誰搶到資源就上鎖,排他性的鎖,其他任務(wù)只能等待;爭搶是一種高并發(fā)的解決方案,但是這樣不好,因?yàn)榭赡苡腥蝿?wù)長期霸占資源,有人一直搶不到資源;
(3)預(yù)處理:將各個任務(wù)會用到的熱點(diǎn)數(shù)據(jù)提前緩存,這樣就減少了任務(wù)的執(zhí)行時間;
(4)并行:開辟多的資源,例如銀行辦理業(yè)務(wù),排隊(duì)的人多了,可以多增加業(yè)務(wù)窗口;此方案為水平擴(kuò)展的思想;
(5)提速:簡單的就是提高資源性能,提高cpu性能或者單個服務(wù)器安裝更多的cpu,此方案為垂直擴(kuò)展
(6)消息中間件:模型可以理解為北京地鐵站外面的排隊(duì)走廊,具有緩沖人流量的功能;常見的消息隊(duì)列有:RabbitMQ、ActiveMQ、RocketMQ(阿里)、kafka等
2、進(jìn)程與線程
# 進(jìn)程與線程的關(guān)系:
程序是源碼編譯后的文件,而這些文件被存放在磁盤上;當(dāng)程序被操作系統(tǒng)加載到內(nèi)存中,就是進(jìn)程,進(jìn)程中存放著指令和數(shù)據(jù)(資源),進(jìn)程是線程的容器;線程是操作系統(tǒng)進(jìn)行運(yùn)算調(diào)度的最小單位;
2.1、線程的狀態(tài)
2.2、python中的進(jìn)程與線程
# 進(jìn)程會啟動一個解釋器進(jìn)程,線程共享一個解釋器進(jìn)程;會有一個GIL,全局解釋器鎖,來分配具體線程任務(wù)執(zhí)行;python的GIL保證同一時刻只有一個線程被執(zhí)行;
3、Python的多線程開發(fā)
3.1、Thread類
# Python的線程開發(fā)使用標(biāo)準(zhǔn)款threading;
# Thread類的初始化方法
def __init__(self, group=None, target=None, name=None,args=(), kwargs=None, *, daemon=None):參數(shù):
target 線程調(diào)用的對象,就是目標(biāo)函數(shù);
name 為線程起名
args 為目標(biāo)函數(shù)傳遞位置參數(shù),元祖;
kwargs 為目標(biāo)函數(shù)傳遞關(guān)鍵字參數(shù),字典
(1)線程的啟動:
通過threading.Thread創(chuàng)建一個線程對象,target是目標(biāo)函數(shù),name指定線程名稱,到此線程被創(chuàng)建完成,需要調(diào)用start() 方法來啟動線程 ;
import threadingdef work():print('I m working')print('Finished')t=threading.Thread(target=work,name='workerthread') # 創(chuàng)建線程t t.start() # 啟動線程t
(2)線程的退出:
# python沒有提供線程的退出方法,但是線程在以下情況會退出:
①:線程函數(shù)內(nèi)語句執(zhí)行完成;
②:線程函數(shù)中拋出未處理異常;
# python的線程沒有優(yōu)先級、沒有線程組的概念,也不能被銷毀,停止,掛起,也沒有恢復(fù)和中斷;
(3)線程的傳參
# 線程的傳參本質(zhì)上和函數(shù)沒有區(qū)別
import threadingdef add(x,y):print(x+y)t=threading.Thread(target=add,args=(4,5),name='workerthread') # args=(4,5)傳遞位置參數(shù); t.start()(4)threading的屬性和方法
# current_thread()? 返回當(dāng)前線程對象
# main_thread() 返回主線程對象
# active_count() 返回處于alive狀態(tài)的線程數(shù)
# enumerate() 返回所有活著的線程列表,不包括已經(jīng)終止的線程和未開始的線程
# get_ident() 返回當(dāng)前線程的id,非0整數(shù)
(5)Thread實(shí)例的屬性和方法
# name:線程名稱
# ident:線程id,線程啟動后才有id,否則為None
# is_alive():是否存活
# start()方法與run()方法
①:start() 啟動線程,每一個線程必須并且只能執(zhí)行一次該方法;
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() 并不啟動新線程,只是在主線程中調(diào)用了一個函數(shù),調(diào)用定義線程對象中的target函數(shù);
def run(self):try:if self._target:self._target(*self._args, **self._kwargs) # 執(zhí)行target函數(shù)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、多線程
#? 多線程,一個進(jìn)程中如果有多個線程,就是多線程,實(shí)現(xiàn)一種并發(fā);
# 一個進(jìn)程中至少有一個線程,并作為程序的入口,這個線程就是主線程;一個進(jìn)程至少有一個主線程,其他線程為工作線程;
# 線程安全與線程不安全
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()運(yùn)行結(jié)果: 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分析: 看代碼,應(yīng)該是一行行打印,但是很多字符串打印在了一起? 說明print函數(shù)被打斷了,被線程切換打斷了,print函數(shù)分2步,第一步打印字符串,第二步打印換行,就在2步之間被打斷,發(fā)生了線程切換 每個線程是根據(jù)系統(tǒng)調(diào)用cpu分時計(jì)算的,可能出現(xiàn)線程1的函數(shù)還沒有執(zhí)行完成,則cpu資源被線程2給搶占,則去執(zhí)行線程2的函數(shù); 解決方法:字符串是不可變數(shù)據(jù)類型,它作為一個整體不可分割輸出,end=''不再讓print換行,但沒有解決根本問題,應(yīng)為print函數(shù)是線程不安全3.3、daemon線程與non-daemon線程
# 注意:daemon不是linux中的守護(hù)進(jìn)程,daemon屬性必須在start方法之前設(shè)置好;
在主線程運(yùn)行結(jié)束后會檢查是否含有,沒有執(zhí)行完成的non-daemon線程,
如果有則等待non-daemon線程運(yùn)行完成;
沒有則直接結(jié)束線程;
# 總結(jié):
①:線程具有一個daemon屬性,可以顯示設(shè)置為True或False,也可以不設(shè)置,則默認(rèn)為None;
②:如果不設(shè)置daemon,就取當(dāng)前線程的daemon來設(shè)置它;
③:主線程是non-daemon,即daemon=False;
④:從主線程創(chuàng)建的所有線程不設(shè)置daemon,則默認(rèn)daemon=False,也就是non-daemon線程;
⑤:Python程序在沒有活的non-daemon線程運(yùn)行時退出,也就是剩下的只能是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線程結(jié)束以后才結(jié)束; print('main thread end')''' 運(yùn)行結(jié)果: 0 1 2 . . 9 main thread end分析:由于t1.join()方法后,將主線程阻塞,等待t1線程結(jié)束; '''# join(timeout=None),是線程的標(biāo)準(zhǔn)方法之一;
# 一個線程中調(diào)用另外一個線程的join方法,調(diào)用著將被阻塞,直到被調(diào)用線程終止;
# 一個線程可以被join多次,timout參數(shù)指定調(diào)用則等待多久,沒有設(shè)置超時時間,就一直等待到被調(diào)用者線程終止;設(shè)置超時時間如果被調(diào)線程沒有結(jié)束,則調(diào)用者不等待,直接結(jié)束線程;
# 調(diào)用誰的join方法,就是join誰,就要等待誰;
4、threading.local類
# python提供的threading.local類,將這個類實(shí)例化得到一個全局對象,但是不同的線程使用這個對象存儲數(shù)據(jù)其他線程看不見;
# 本質(zhì):threading.local類構(gòu)建了一個大字典,存放所有線程相關(guān)的字典,定義如下: { 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()''' 運(yùn)行結(jié)構(gòu): <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的子類,就是線程類,具有線程的屬性和方法;
# 它的實(shí)例就是能延時執(zhí)行目標(biāo)函數(shù)的線程,在真正執(zhí)行目標(biāo)函數(shù)之前,都可以cancel它;
# cancel方法本質(zhì)使用Event類實(shí)現(xiàn);
# 自定義實(shí)現(xiàn)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() # 在這里起一個線程是為了避免在主線程被阻塞,執(zhí)行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')?
轉(zhuǎn)載于:https://www.cnblogs.com/soulgou123/p/9877593.html
總結(jié)
以上是生活随笔為你收集整理的Python学习并发与多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hadoop单机/伪分布式集群搭建(新手
- 下一篇: 2-4 zookeeper配置文件介绍,