溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

python線程基礎(chǔ)

發(fā)布時間:2020-06-27 16:43:11 來源:網(wǎng)絡(luò) 閱讀:1964 作者:長跑者1號 欄目:編程語言

一 基本概念

1 并行和并發(fā)

1 并行,parallel

同時做某些事,可以互不干擾的同一時刻做幾件事
如高速公路上的車道,同一時刻,可以有多個互不干擾的車運(yùn)行
在同一時刻,每條車道上可能同時有車輛在跑,是同時發(fā)生的概念

2 并發(fā),concurrency

也是同時做某事,但強(qiáng)調(diào)的是同一時段做了幾件事。
并行是可以解決并發(fā)問題的。

2 并發(fā)的解決

1 隊列,緩沖區(qū)

隊列:排隊就是隊列,先進(jìn)先出,解決了資源使用的問題。
緩沖區(qū):排程的隊列,其實就是一個緩沖地帶,就是緩沖區(qū)
優(yōu)先隊列:對比較重要的事進(jìn)行及時的處理,此處就是優(yōu)先隊列

2 爭搶

只開一個窗口,有可能沒秩序,也就是誰擠進(jìn)去就給誰打飯
擠到窗口的人占據(jù)窗口,直到達(dá)到飯菜離開,其他人繼續(xù)爭搶,會有一個人占據(jù)窗口,可以視為鎖定窗口,窗口就不能為其他人提供服務(wù)了,這是一種鎖機(jī)制,搶到資源就上鎖,排他性鎖,其他人只能等候

爭搶也是一種高并發(fā)解決方案,但是,不好,因為有人可能長時間搶不到。

3 預(yù)處理

一種提前加載用戶需要的數(shù)據(jù)的思路,如預(yù)熱,預(yù)加載等,緩存中常用
緩存的思想就是將數(shù)據(jù)直接拿到,進(jìn)行處理。

4 并行

可通過購買更多的服務(wù)器,或開多線程,進(jìn)行實現(xiàn)并行處理,來解決并發(fā)問題,這些都是水平擴(kuò)展,

5 提速

提高單個CPU性能,或者單個服務(wù)器安裝更多的CPU,但此和多個服務(wù)器相比成本較高

6 消息中間件

通過中間的緩沖器來解決并發(fā)問題,如rabbitmq,activemq,rocketmq,kafka 等,CDN也算是一種

3 進(jìn)程和線程概念

1 進(jìn)程和線程

在實現(xiàn)了線程的操作系統(tǒng)中,線程是操作系統(tǒng)能夠運(yùn)算調(diào)度的最小單位,他被包含在進(jìn)程中,是進(jìn)程中的實際運(yùn)作單位,一個程序的執(zhí)行實例就是一個進(jìn)程


進(jìn)程(process)是計算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)

2 進(jìn)程和線程的關(guān)系

程序是源代碼編譯后的文件,而這些文件存放在磁盤上,當(dāng)程序被操作系統(tǒng)加載到內(nèi)存中,就是進(jìn)程,進(jìn)程中存放著指令和數(shù)據(jù)(資源),它也是線程的容器。


Linux進(jìn)程有父進(jìn)程,子進(jìn)程,windows中進(jìn)程之間是平等關(guān)系


線程有時候被稱為輕量級進(jìn)程(LWP),是程序執(zhí)行的最小單元,一個標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成

3 進(jìn)程,線程的理解

現(xiàn)代操作系統(tǒng)提出進(jìn)程的概念,每一個進(jìn)程都認(rèn)為自己獨(dú)占所有計算機(jī)硬件資源,進(jìn)程就是獨(dú)立王國,進(jìn)程間不能隨便共享數(shù)據(jù)
線程就是省份,同一個進(jìn)程內(nèi)的線程可以共享進(jìn)程的資源,每一個線程擁有自己獨(dú)立的堆棧。

4 python中的進(jìn)程和線程

進(jìn)程會啟動一個解釋器進(jìn)程,線程共享一個解釋器進(jìn)程

兩個解釋器進(jìn)程之間是沒有任何關(guān)系的,不同進(jìn)程之間是不能隨便交互數(shù)據(jù)的
大多數(shù)數(shù)據(jù)都是跑在主線程上的

4 線程的狀態(tài)

1 概述

1 運(yùn)行態(tài): 該時刻,該線程正在占用CPU資源
2 就緒態(tài):可隨時轉(zhuǎn)換成運(yùn)行態(tài),因為其他線程正在運(yùn)行而暫停,該線程不占CPU
3 阻塞態(tài): 除非外部某些事情發(fā)生,否則線程不能運(yùn)行
4 終止: 線程完成,或退出,或被取消

2 線程狀態(tài)轉(zhuǎn)換

python線程基礎(chǔ)

先創(chuàng)建進(jìn)程,然后再創(chuàng)建一個線程
等待資源的運(yùn)行
阻塞不能直接進(jìn)入運(yùn)行狀態(tài),必須先進(jìn)入就緒狀態(tài)
運(yùn)行中的線程是可以被取消的

二 python線程開發(fā)

1 Thread類

簽名

def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None):

參數(shù)名及含義:
target:線程調(diào)用的對象,就是目標(biāo)函數(shù)
name:為線程起名字(不同線程的名字可以重復(fù),主要是通過線程TID進(jìn)行區(qū)分的)
args:為目標(biāo)函數(shù)傳遞參數(shù),元祖
kwargs: 為目標(biāo)函數(shù)關(guān)鍵字傳參,字典

2 實例

1 基本創(chuàng)建

實例如下

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
def  test():
    for i in range(5):
        print (i)
    print ('Thread over')

# 實例化一個線程
t=threading.Thread(target=test)
t.start() # 啟動一個線程

python線程基礎(chǔ)

隨著函數(shù)的執(zhí)行完成,線程也就結(jié)束了,子線程不結(jié)束,則主線程一直存在,此時的主線程是等待狀態(tài)


通過threading.Thread創(chuàng)建一個線程對象,target是目標(biāo)函數(shù),name可以指定名稱,但是線程沒有啟動,需要調(diào)用start方法。
線程之所以能執(zhí)行函數(shù),是因為線程中就是執(zhí)行代碼,而最簡單的封裝就是哈函數(shù),所以還是函數(shù)調(diào)用。


函數(shù)執(zhí)行完成,線程就退出了,如果不讓線程退出,則需要使用死循環(huán)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
def  test():
    for i in range(5):
        print (i)
    print ('Thread over')

# 實例化一個線程
t=threading.Thread(target=test,name='test1')
t.start() # 啟動一個線程
t=threading.Thread(target=test,name='test2')
t.start() # 啟動一個線程

# 上述兩個線程是并行處理,如果是一個CPU,則是假的平衡

結(jié)果如下

python線程基礎(chǔ)

2 線程退出

python中沒有提供線程退出的方式,線程在下面情況時退出、
1 線程函數(shù)內(nèi)語句執(zhí)行完畢
2 線程函數(shù)中拋出未處理的異常

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
def  test():
    count=0
    while True:
        count+=1
        if  count==3:
            raise Exception('NUMBER')
        print (count)
# 實例化一個線程
t=threading.Thread(target=test,name='test1')
t.start() # 啟動一個線程

異常導(dǎo)致的線程退出

python線程基礎(chǔ)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
def  test():
    count=0
    while True:
        count+=1
        if  count==3:
            raise Exception('NUMBER')
        print (count)
def test1():
    for i in range(5):
        time.sleep(0.1)
        print ('test1',i)
# 實例化一個線程
t=threading.Thread(target=test,name='test')
t.start() # 啟動一個線程
t=threading.Thread(target=test1,name='test1')  #此處啟用一個線程,看上述線程能否影響該線程的運(yùn)行情況
t.start()

結(jié)果如下

python線程基礎(chǔ)

python中線程沒有優(yōu)先級,沒有線程組的概念,也不能被銷毀,停止,掛起,也就沒有恢復(fù),中斷了,上述的一個線程的異常不能影響另一個線程的運(yùn)行,另一個線程的運(yùn)行是因為其函數(shù)運(yùn)行完成了

3 線程傳參

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
def  test(count):
    while True:
        count+=1
        if  count==5:
            raise Exception('NUMBER')
        print (count)
# 實例化一個線程
t=threading.Thread(target=test,name='test',args=(0,))  #此處必須是元祖類型,否則會報錯 
t.start() # 啟動一個線程

python線程基礎(chǔ)

4 線程相關(guān)屬性

current_thread() 返回當(dāng)前線程對象
main_thread() 返回主線程對象
active_count() 當(dāng)前處于alive狀態(tài)的線程個數(shù)
enumerate() 返回所有活著的線程的列表,不包括已經(jīng)終止的線程和未開始的線程
get_ident() 返回當(dāng)前線程的ID,非0整數(shù)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
def  test(count):
    while True:
        print ("當(dāng)前線程對象為{}當(dāng)前處于活動的線程個數(shù)為{}".format(threading.current_thread(),threading.active_count()))
        count+=1
        if  count==5:
            break
        print (count)
    print('當(dāng)前活著的線程列表為:', threading.enumerate())

# 實例化一個線程
t=threading.Thread(target=test,name='test',args=(0,))  #此處必須是元祖類型,否則會報錯
t.start() # 啟動一個線程
print ('當(dāng)前活著的線程列表為:',threading.enumerate())

print ('當(dāng)前處于活動的線程個數(shù)為{} ,當(dāng)前主線程為{},當(dāng)前線程ID為{}'.format(threading.active_count(),threading.main_thread(),threading.get_ident()))

結(jié)果如下

python線程基礎(chǔ)

其線程的執(zhí)行不是順序的,其調(diào)用取決于CPU的調(diào)度規(guī)則,而主線程在子線程所有子線程退出之前都是active狀態(tài)。

5 線程實例的屬性和方法(getname和setname)

name : 線程的名字,只是一個標(biāo)識,其可以重名,getname() 獲取,setname()設(shè)置這個名詞

ident:線程ID,其是非0整數(shù),線程啟動后才會有ID,否則為None,線程退出,此ID依舊可以訪問,此ID可以重復(fù)使用
is_alive() 返回線程是否活著

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
def  test(count):
    while True:
        count+=1
        if  count==5:
            break
        print (count)
    print ('當(dāng)前線程name 為{},ID 為{}'.format(threading.current_thread().name,threading.current_thread().ident))

# 實例化一個線程
t=threading.Thread(target=test,name='test',args=(0,))  #此處必須是元祖類型,否則會報錯
t.start() # 啟動一個線程
print  ('主線程狀態(tài)',threading.main_thread().is_alive())
print ('線程狀態(tài)',threading.current_thread().is_alive())

結(jié)果如下

python線程基礎(chǔ)

3 start 和run 的區(qū)別與聯(lián)系

1 基本概述

start() 啟動線程,每一個線程必須且只能被執(zhí)行一次

run() 運(yùn)行線程函數(shù)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('本線程ID為{},主線程ID為{}'.format(threading.current_thread().ident,threading.main_thread().ident))
    print ('test')

t=MyThread(target=work,name='w')
t.start()

結(jié)果如下

python線程基礎(chǔ)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('本線程ID為{},主線程ID為{}'.format(threading.current_thread().ident,threading.main_thread().ident))
    print ('test')

t=MyThread(target=work,name='w')
t.run()

結(jié)果如下

python線程基礎(chǔ)

結(jié)論如下:start 方法的調(diào)用會產(chǎn)生新的線程,而run的調(diào)用是在主線程中運(yùn)行的,且run的調(diào)用只會調(diào)用自己的方法,而start 會調(diào)用自己和run方法

2 run 和 start 調(diào)用次數(shù)問題

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.start()
time.sleep(3)
t.start() #再次啟用線程

python線程基礎(chǔ)

上述可知,線程在start是會調(diào)用start和run屬性運(yùn)行,且其不能再次啟動線程一次。


調(diào)用run方法

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.run()
time.sleep(3)
t.run()

結(jié)果如下

python線程基礎(chǔ)

run 方法也只能調(diào)用一次

3 start和run 合用

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.run()
time.sleep(3)
t.start()

結(jié)果如下

python線程基礎(chǔ)

上述結(jié)果表明,run和start的調(diào)用不能出現(xiàn)在同一個線程中

4 解決同一代碼中調(diào)用問題

重新構(gòu)建一個新線程并啟動

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.start()
t=MyThread(target=work,name='w1')
t.start()

結(jié)果如下

python線程基礎(chǔ)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.run()
t=MyThread(target=work,name='w1')
t.run()

結(jié)果如下

python線程基礎(chǔ)

5 run 和start 的作用

注釋繼承的run方法

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        super().start()

    def run(self) -> None:
        print ('run',self)
       # super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.start()
t=MyThread(target=work,name='w1')
t.start()

結(jié)果如下

python線程基礎(chǔ)

禁用start方法

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關(guān)start和run屬性
    def start(self) -> None:
        print ('start',self)
        #super().start()

    def run(self) -> None:
        print ('run',self)
        super().run()
def  work():
    print ('test')

t=MyThread(target=work,name='w')
t.start()
t=MyThread(target=work,name='w1')
t.start()

python線程基礎(chǔ)

結(jié)論:start()函數(shù)會調(diào)用run函數(shù),而run()函數(shù)是用來運(yùn)行函數(shù)的,start是創(chuàng)建線程的,在執(zhí)行start()時run()必不可少,而在運(yùn)行run()時因為不需要調(diào)用start(),因此其是非必須的。


start 會啟用新的線程,其使用可以形成多線程,而run()是在當(dāng)前線程中調(diào)用函數(shù),不會產(chǎn)生新的線程,其均不能多次調(diào)用

4 多線程概述

一個進(jìn)程中如果有多個線程,就是多線程,實現(xiàn)一種并發(fā)

線程的調(diào)度任務(wù)是操作系統(tǒng)完成的

沒有開新的線程,這就是普通的函數(shù)調(diào)用,所以執(zhí)行完t1.run(),然后執(zhí)行t2.run(),這不是多線程

當(dāng)使用start方法啟動線程時,進(jìn)程內(nèi)有多個活動的線程并行工作,就是多線程

一個進(jìn)程中至少有一個線程,作為程序的入口,這個線程就是主線程,一個進(jìn)程至少有一個主線程

其他線程稱為工作線程

python中的線程沒有優(yōu)先級的概念

5 線程安全

1 問題

此實例需要在ipython 中運(yùn)行

python線程基礎(chǔ)

此處的print 會被打斷,其中間有空格,此種情況稱為線程不安全。
print 函數(shù)的執(zhí)行分為兩步:
1 打印字符串
2 換行,就在這之間發(fā)生了線程切換,其不安全

2 解決方式:

1 通過字符串的拼接來完成

python線程基礎(chǔ)

2 通過logging模塊來處理,其輸出過程中是不被打斷的

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認(rèn)是WARNING,此處修改為INFO 
def woker():
    for  x  in range(10):
        msg="{} is running".format(threading.current_thread())
        logging.info(msg)  # 日志打印 
for x  in range(5):
    t = threading.Thread(target=woker,name="work-{}".format(x)).start()

結(jié)果如下

python線程基礎(chǔ)

簡單測試的時候使用print,在其他應(yīng)用的時候必須使用logging,其是針對日志打印使用的技術(shù),日志打印過程中是不能被中斷的,

6 daemon 線程和 non-daemon線程

1 概述

這里的daemon線程不是Linux中的守護(hù)進(jìn)程


進(jìn)程靠線程執(zhí)行代碼,至少一個主線程,其他線程是工作線程
主線程是第一個啟動的線程
父線程: 如果線程A中啟動了一個線程B,A就是B的父線程
子線程: B就是A的子線程

在python中,構(gòu)建線程的時候,可以設(shè)置daemon屬性,這個屬性必須在start方法之前設(shè)置好,

相關(guān)源碼

python線程基礎(chǔ)

此處表明。若傳入的daemon 不是None,則其表示默認(rèn)傳入的值,否則,及若不傳入,則表示使用當(dāng)前線程的daemon

主線程是non-daemon線程,及daemon=False

活著線程的列表的源碼

python線程基礎(chǔ)

此處表示活著的線程列表中一定會包含主線程,

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認(rèn)是WARNING,此處修改為INFO
def woker():
    for  x  in range(10):
        msg="{} is running".format(threading.current_thread())
        logging.info(msg)  # 日志打印

threading.Thread(target=woker,name="work-{}".format(0)).start()
print  ('ending')
print (threading.enumerate()) #主線程因為其他線程的執(zhí)行,因此其處于等待狀態(tài)

結(jié)果如下

python線程基礎(chǔ)

2 daemon線程

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認(rèn)是WARNING,此處修改為INFO
def woker():
    for  x  in range(10):
        msg="{} is running".format(threading.current_thread())
        logging.info(msg)  # 日志打印

threading.Thread(target=woker,name="work-{}".format(0),daemon=True).start() #主線程一般會在一定時間內(nèi)掃描屬性列表,若其中有non-daemon類型
# 的線程,則會等待其執(zhí)行完成再退出,若是遇見都是daemon類型線程,則直接退出,
print  ('ending')
print (threading.enumerate()) #主線程因為其他線程的執(zhí)行,因此其處于等待狀態(tài)

結(jié)果如下

python線程基礎(chǔ)

上述線程是daemon線程,因此主線程不會等待其完成后再關(guān)閉

3 non-daemon 和 damon

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
import time
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認(rèn)是WARNING,此處修改為INFO
def woker():
    for  x  in range(10):
        msg="{}  {} is running".format(x,threading.current_thread())
        logging.info(msg)  # 日志打印
        time.sleep(0.5)  #此處配置延遲,檢驗是否在non-daemon線程執(zhí)行完成后及會直接關(guān)閉的情況

threading.Thread(target=woker,name="work-{}".format(0),daemon=True).start() #主線程一般會在一定時間內(nèi)掃描屬性列表,若其中有non-daemon類型
# 的線程,則會等待其執(zhí)行完成再退出,若是遇見都是daemon類型線程,則直接退出,、
def woker1():
    for  x  in ['a','b','c','d']:
        msg="{}  {} is running".format(x,threading.current_thread())
        logging.info(msg)  # 日志打印

threading.Thread(target=woker1,name="work-{}".format(0)).start() #主線程一般會在一定時間內(nèi)掃描屬性列表,若其中有non-daemon類型,則不會終止,
# 此處默認(rèn)從父線程中獲取屬性,父線程中是non-daemon,因此此屬性會一直運(yùn)行,上面的會關(guān)閉,但不會影響這個

print  ('ending')
print (threading.enumerate()) #主線程因為其他線程的執(zhí)行,因此其處于等待狀態(tài)

結(jié)果如下

python線程基礎(chǔ)

結(jié)果表示,當(dāng)non-daemon線程執(zhí)行完成后,不管damon是否執(zhí)行完成,主線程將直接終止,不會再次運(yùn)行。

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
import time
def woker1():
    for  x  in ['a','b','c','d']:
        msg="{}  {} is running".format(x,threading.current_thread())
        logging.info(msg)  # 日志打印

logging.basicConfig(level=logging.INFO)  #定義基本級別,默認(rèn)是WARNING,此處修改為INFO
def woker():

    for  x  in range(10):
        msg="{}  {} is running".format(x,threading.current_thread())
        logging.info(msg)  # 日志打印
        time.sleep(1)  # 此處配置1秒延時,使得主線程看不到孫子線程的non-daemon就關(guān)閉
    T3=threading.Thread(target=woker1,name="woker{}".format(10),daemon=False)  #此處啟動的線程默認(rèn)是non-daemon線程,但由于其父線程是daemon
    # 及就是下面的T1線程,當(dāng)T2線程執(zhí)行完畢后線程掃描,發(fā)現(xiàn)沒non-daemon線程,則直接退出,此時將不會繼續(xù)執(zhí)行T1 的子線程T3,雖然T3是non-daemon。因為其未啟動
    T3.start()

T1=threading.Thread(target=woker,name="work-{}".format(0),daemon=True)#主線程一般會在一定時間內(nèi)掃描屬性列表,若其中有non-daemon類型
T1.start()
# 的線程,則會等待其執(zhí)行完成再退出,若是遇見都是daemon類型線程,則直接退出,

T2=threading.Thread(target=woker1,name="work-{}".format(0)) #主線程一般會在一定時間內(nèi)掃描屬性列表,若其中有non-daemon類型,則不會終止,
# 此處默認(rèn)從父線程中獲取屬性,父線程中是non-daemon,因此此屬性會一直運(yùn)行,上面的會關(guān)閉,但不會影響這個
T2.start()
print  ('ending')
print (threading.enumerate()) #主線程因為其他線程的執(zhí)行,因此其處于等待狀態(tài)

結(jié)果如下

python線程基礎(chǔ)

可能孫子線程還沒起來,主線程只看到了daemon線程。則直接進(jìn)行關(guān)閉,

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
import time
def woker1():
    for  x  in ['a','b','c','d']:
        msg="{}  {} is running".format(x,threading.current_thread())
        logging.info(msg)  # 日志打印

logging.basicConfig(level=logging.INFO)  #定義基本級別,默認(rèn)是WARNING,此處修改為INFO
def woker():

    for  x  in range(10):
        msg="{}  {} is running".format(x,threading.current_thread())
        logging.info(msg)  # 日志打印
        # time.sleep(1)  # 此處配置1秒延時,使得主線程看不到孫子線程的non-daemon就關(guān)閉
    T3=threading.Thread(target=woker1,name="woker{}".format(10),daemon=False)  #此處啟動的線程默認(rèn)是non-daemon線程,但由于其父線程是daemon
    # 及就是下面的T1線程,當(dāng)T2線程執(zhí)行完畢后線程掃描,發(fā)現(xiàn)沒non-daemon線程,則直接退出,此時將不會繼續(xù)執(zhí)行T1 的子線程T3,雖然T3是non-daemon。因為其未啟動
    T3.start()

T1=threading.Thread(target=woker,name="work-{}".format(0),daemon=True)#主線程一般會在一定時間內(nèi)掃描屬性列表,若其中有non-daemon類型
T1.start()
# 的線程,則會等待其執(zhí)行完成再退出,若是遇見都是daemon類型線程,則直接退出,

結(jié)果如下

python線程基礎(chǔ)

也可能是孫子線程已經(jīng)起來了,主線程看到了non-daemon線程,因此未直接關(guān)閉,而是等待孫子線程執(zhí)行完成后才進(jìn)行關(guān)閉操作


相關(guān)屬性

daemon 屬性 表示線程是否是daemon線程,這個值必須在start()之前設(shè)置,否則會引發(fā)異常
isDaemon() 是否是daemon線程
setDaemon() 設(shè)置為daemon線程,必須在start方法之前設(shè)置


總結(jié):

python中父線程和子線程沒有直接的管理關(guān)系

python主線程是否殺掉線程,看的是daemon,若只有daemon,則直接刪掉所有線程,自己結(jié)束,若還有子線程是non-daemon,則會等待

如果想讓一個線程完整執(zhí)行,則需要定義non-daemon屬性

daemon 屬性,必須在start 之前設(shè)置,否則會引發(fā)runtimeError異常

線程具有daemon屬性,可以顯示設(shè)置為True或False,也可以不設(shè)置,則去默認(rèn)值None
如果不設(shè)置daemon,就區(qū)當(dāng)前線程的daemon來設(shè)置它

主線程是non-daemon線程,及daemon=False

從主線程創(chuàng)建的所有線程不設(shè)置daemon屬性,則默認(rèn)都是daemon=False,也就是non-daemon線程

python程序在沒有活著的non-daemon線程運(yùn)行時推出,也就是剩下的只有daemon線程,主線程才能退出,否則主線程就只能等待。


應(yīng)用場景:
不關(guān)心什么時候開始,什么時候結(jié)束的時候使用daemon,否則可以使用non-daemon

Linux的daemon是進(jìn)程級別的,而python的daemon是線程級別的,其之間沒有可比性的

daemon和non-daemon 啟動的時候,需要注意啟動的時機(jī)。


簡單來說,本來并沒有daemon thread,為了簡化程序員工作,讓他們不去記錄和管理那些后臺線程,創(chuàng)造了daemon thread 的概念,這個概念唯一的作用就是,當(dāng)你把一個線程設(shè)置為daemon時,它會隨著主線程的退出而退出。


主要應(yīng)用場景:
1 后臺任務(wù),發(fā)送心跳包,監(jiān)控,這種場景較多。
2 主線程工作才有用的線程,如主線程中維護(hù)了公共資源,主線程已經(jīng)清理了,準(zhǔn)備退出,而工作線程使用這些資源工作也沒意義了,一起退出最合適
3 隨時可以被終止的線程

7 join

join是標(biāo)準(zhǔn)的線程函數(shù)之一,其含義是等待,誰調(diào)用join,誰等待

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導(dǎo)入日志打印模塊
import time
def  foo(n):
    for i in range(n):
        print (i)
        time.sleep(0.5)
t1=threading.Thread(target=foo,args=(10,),daemon=True)
t1.start()  # 默認(rèn)情況下,此線程只能執(zhí)行少量此,一般不能全部執(zhí)行
t1.join()  # 通過join方法將原本不能執(zhí)行完成的線程執(zhí)行完成了

結(jié)果如下

python線程基礎(chǔ)

使用join方法,daemon線程執(zhí)行完成后,主線程才退出,

join(timeout=None),是線程的標(biāo)準(zhǔn)方法之一。
timeout參數(shù)指定調(diào)用者等待多久,沒有設(shè)置超時,則就一直等到被調(diào)用線程結(jié)束,調(diào)用誰的join方法,就是join誰,誰就要等待。

一個線程中調(diào)用另一個線程的join方法,調(diào)用者將被阻塞,直到被調(diào)用者線程終止,一個線程可以被join多次

如果在一個daemon C 線程中,對另一個daemon線程D 使用了join方法,只能說明C要等待D,主線程退出,C和D是否結(jié)束,也不管他們誰等待誰,都要被殺掉。

join 方法,支持使用等待,但其會導(dǎo)致多線程變成單線程,其會影響正常的運(yùn)行,因此一般會將生成的線程加入到列表中,進(jìn)行遍歷得到對應(yīng)線程進(jìn)行計算。

8 threading.local 類

python 提供了threading.local 類,將這個實例化得到一個全局對象,但是不同的線程,這個對象存儲的數(shù)據(jù)其他線程看不到

1 局部變量

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
def  worker():
    x=0  # 此處是局部變量
    for i in range(10):
        time.sleep(0.0001)
        x+=1
    print (threading.current_thread(),x)
for i in range(10):
    threading.Thread(target=worker).start()

結(jié)果如下

python線程基礎(chǔ)

2 全局變量

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
x = 0  # 此處是一個全局變量
def  worker():
    for i in range(10):
        global x
        time.sleep(0.0001)
        x+=1
    print (threading.current_thread(),x)
for i in range(10):
    threading.Thread(target=worker).start()

結(jié)果如下

python線程基礎(chǔ)

局部變量本身具有隔離效果,一旦變成全局變量,則所有的線程都將能夠訪問和修改。

3 使用類處理

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
class  A:
    def __init__(self,x):
        self.x=x
a=A(0)
def  worker():
    for i in range(100):
        a.x=0
        time.sleep(0.0001)
        a.x+=1
    print (threading.current_thread(),a.x)
for i in range(10):
    threading.Thread(target=worker).start()

結(jié)果如下

python線程基礎(chǔ)

其不同線程的TID是不同的,可通過不同線程的TID進(jìn)行為鍵,其結(jié)果為值,便可解決此種亂象

4 threading.local

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
a=threading.local()  # 做到隔離,通過TID進(jìn)行數(shù)據(jù)的隔離處理不同線程的不同數(shù)值問題
def  worker():
    a.x = 0
    for i in range(100):
        time.sleep(0.0001)
        a.x+=1
    print (threading.current_thread(),a.x)
for i in range(10):
    threading.Thread(target=worker).start()

結(jié)果如下

python線程基礎(chǔ)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
a=threading.local()  # 做到隔離,通過TID進(jìn)行數(shù)據(jù)的隔離處理不同線程的不同數(shù)值問題
def  worker():
    a.x = 0
    for i in range(100):
        time.sleep(0.0001)
        a.x+=1
    print (threading.current_thread(),a.x)
    print (threading.get_ident(),a.__dict__) #此處打印線程TID和字典
for i in range(10):
    threading.Thread(target=worker).start()

結(jié)果如下

python線程基礎(chǔ)

5 源代碼

python線程基礎(chǔ)
python線程基礎(chǔ)

self.key 是 前面的加上id
通過字典實現(xiàn),線程ID的地址是唯一的,但跨進(jìn)程的線程ID 不一定是相同的

進(jìn)程中的線程地址可能是一樣的。每一個進(jìn)程都認(rèn)為自己是獨(dú)占資源的,但不一定就是 。

6 實踐

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
X='abc'
ctx=threading.local()
ctx.x=123
def work():
    print (X)
    print (ctx)
    print (ctx.x)  #此時的字典中ctx此ctx.x屬性,因此其不能打印,其是在線程內(nèi)部,每個dict對應(yīng)的值都是獨(dú)立的
    print ('end')
threading.Thread(target=work).run()  # 此處是本地線程調(diào)用,則不會影響
threading.Thread(target=work).start()

結(jié)果如下

python線程基礎(chǔ)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
X='abc'
ctx=threading.local()
ctx.x=123
def work():
    print (X)
    print (ctx)
    ctx.x=100 #內(nèi)部線程中定義一個局部變量,則可以執(zhí)行和被調(diào)用
    print (ctx.x)  #此時的ctx 無此屬性,因此其不能打印,其是在線程內(nèi)部,
    print ('end')
threading.Thread(target=work).run()  # 此處是本地線程調(diào)用,則不會影響
threading.Thread(target=work).start()

結(jié)果如下

python線程基礎(chǔ)

7 結(jié)論

threading.local類構(gòu)件了一個大字典,其元素的每一線程實例的地址為Key和線程的引用線程單獨(dú)的字典的映射(棧),通過threading.local 實例就可以在不同的線程中,安全的使用線程獨(dú)有的數(shù)據(jù),做到了線程間數(shù)據(jù)的隔離,如同本地變量一樣

8 延遲執(zhí)行Timter

1 源碼

python線程基礎(chǔ)

上述可看到,其第一個字段便是時間

2 基本實例

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import datetime
start_time=datetime.datetime.now()

def  add(x,y):
    print   (x+y)
    print("函數(shù)執(zhí)行時間為{}".format((datetime.datetime.now() - start_time).total_seconds()))

t=threading.Timer(3,add,args=(3,4))
t.start()  #此處會延遲3秒執(zhí)行

結(jié)果如下

python線程基礎(chǔ)

此處是延遲執(zhí)行線程,而不是延遲執(zhí)行函數(shù),本質(zhì)上還是線程

3 t.cancel() 線程的刪除

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import datetime
import time
def  add(x,y):
    print   (x+y)
t=threading.Timer(6,add,args=(3,4)) # 此處表示6秒后出結(jié)果
t.start()
time.sleep(5) 
t.cancel() #線程被刪除

只要是沒真正執(zhí)行的線程,都能夠被cancel刪除

python線程基礎(chǔ)

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import datetime
import time
def  add(x,y):
    time.sleep(5)
    print   (x+y)
t=threading.Timer(6,add,args=(3,4)) # 此處表示6秒后出結(jié)果
t.start()
time.sleep(10)
t.cancel()

結(jié)果如下
python線程基礎(chǔ)

start方法后,timer對象會處于等待狀態(tài),等待interval之后,開始執(zhí)行function函數(shù),如果在執(zhí)行函數(shù)之前等待階段,使用了cancel方法,就會跳過執(zhí)行函數(shù)結(jié)束。
如果線程已經(jīng)開始執(zhí)行了,則cancel就沒有任何效果了

4 總結(jié)

Timer是線程Thread的子類,就是線程類,具有線程的能力和特征
它的實例是能夠延遲執(zhí)行目標(biāo)函數(shù)的線程,在真正的執(zhí)行目標(biāo)函數(shù)之前,都可以cancel它 。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI