python線程基礎

一 基本概念

1 並行和併發

1 並行,parallel

同時作某些事,能夠互不干擾的同一時刻作幾件事
如高速公路上的車道,同一時刻,能夠有多個互不干擾的車運行
在同一時刻,每條車道上可能同時有車輛在跑,是同時發生的概念 python

2 併發,concurrency

也是同時作某事,但強調的是同一時段作了幾件事。
並行是能夠解決併發問題的。程序員

2 併發的解決

1 隊列,緩衝區

隊列:排隊就是隊列,先進先出,解決了資源使用的問題。
緩衝區:排程的隊列,其實就是一個緩衝地帶,就是緩衝區
優先隊列:對比較重要的事進行及時的處理,此處就是優先隊列 windows

2 爭搶

只開一個窗口,有可能沒秩序,也就是誰擠進去就給誰打飯
擠到窗口的人佔據窗口,直到達到飯菜離開,其餘人繼續爭搶,會有一我的佔據窗口,能夠視爲鎖定窗口,窗口就不能爲其餘人提供服務了,這是一種鎖機制,搶到資源就上鎖,排他性鎖,其餘人只能等候 緩存

爭搶也是一種高併發解決方案,可是,很差,由於有人可能長時間搶不到。安全

3 預處理

一種提早加載用戶須要的數據的思路,如預熱,預加載等,緩存中經常使用
緩存的思想就是將數據直接拿到,進行處理。服務器

4 並行

可經過購買更多的服務器,或開多線程,進行實現並行處理,來解決併發問題,這些都是水平擴展,多線程

5 提速

提升單個CPU性能,或者單個服務器安裝更多的CPU,但此和多個服務器相比成本較高併發

6 消息中間件

經過中間的緩衝器來解決併發問題,如rabbitmq,activemq,rocketmq,kafka 等,CDN也算是一種ide

3 進程和線程概念

1 進程和線程

在實現了線程的操做系統中,線程是操做系統可以運算調度的最小單位,他被包含在進程中,是進程中的實際運做單位,一個程序的執行實例就是一個進程 函數


進程(process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎

2 進程和線程的關係

程序是源代碼編譯後的文件,而這些文件存放在磁盤上,當程序被操做系統加載到內存中,就是進程,進程中存放着指令和數據(資源),它也是線程的容器。


Linux進程有父進程,子進程,windows中進程之間是平等關係


線程有時候被稱爲輕量級進程(LWP),是程序執行的最小單元,一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成

3 進程,線程的理解

現代操做系統提出進程的概念,每個進程都認爲本身獨佔全部計算機硬件資源,進程就是獨立王國,進程間不能隨便共享數據
線程就是省份,同一個進程內的線程能夠共享進程的資源,每個線程擁有本身獨立的堆棧。

4 python中的進程和線程

進程會啓動一個解釋器進程,線程共享一個解釋器進程

兩個解釋器進程之間是沒有任何關係的,不一樣進程之間是不能隨便交互數據的
大多數數據都是跑在主線程上的

4 線程的狀態

1 概述

1 運行態: 該時刻,該線程正在佔用CPU資源
2 就緒態:可隨時轉換成運行態,由於其餘線程正在運行而暫停,該線程不佔CPU
3 阻塞態: 除非外部某些事情發生,不然線程不能運行
4 終止: 線程完成,或退出,或被取消

2 線程狀態轉換

python線程基礎

先建立進程,而後再建立一個線程
等待資源的運行
阻塞不能直接進入運行狀態,必須先進入就緒狀態
運行中的線程是能夠被取消的

二 python線程開發

1 Thread類

簽名

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

參數名及含義:
target:線程調用的對象,就是目標函數
name:爲線程起名字(不一樣線程的名字能夠重複,主要是經過線程TID進行區分的)
args:爲目標函數傳遞參數,元祖
kwargs: 爲目標函數關鍵字傳參,字典

2 實例

1 基本建立

實例以下

#!/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線程基礎

隨着函數的執行完成,線程也就結束了,子線程不結束,則主線程一直存在,此時的主線程是等待狀態


經過threading.Thread建立一個線程對象,target是目標函數,name能夠指定名稱,可是線程沒有啓動,須要調用start方法。
線程之因此能執行函數,是由於線程中就是執行代碼,而最簡單的封裝就是哈函數,因此仍是函數調用。


函數執行完成,線程就退出了,若是不讓線程退出,則須要使用死循環

#!/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,則是假的平衡

結果以下

python線程基礎

2 線程退出

python中沒有提供線程退出的方式,線程在下面狀況時退出、
1 線程函數內語句執行完畢
2 線程函數中拋出未處理的異常

#!/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() # 啓動一個線程

異常致使的線程退出

python線程基礎

#!/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')  #此處啓用一個線程,看上述線程可否影響該線程的運行狀況
t.start()

結果以下

python線程基礎

python中線程沒有優先級,沒有線程組的概念,也不能被銷燬,中止,掛起,也就沒有恢復,中斷了,上述的一個線程的異常不能影響另外一個線程的運行,另外一個線程的運行是由於其函數運行完成了

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線程基礎

4 線程相關屬性

current_thread() 返回當前線程對象
main_thread() 返回主線程對象
active_count() 當前處於alive狀態的線程個數
enumerate() 返回全部活着的線程的列表,不包括已經終止的線程和未開始的線程
get_ident() 返回當前線程的ID,非0整數

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

# 實例化一個線程
t=threading.Thread(target=test,name='test',args=(0,))  #此處必須是元祖類型,不然會報錯
t.start() # 啓動一個線程
print ('當前活着的線程列表爲:',threading.enumerate())

print ('當前處於活動的線程個數爲{} ,當前主線程爲{},當前線程ID爲{}'.format(threading.active_count(),threading.main_thread(),threading.get_ident()))

結果以下

python線程基礎

其線程的執行不是順序的,其調用取決於CPU的調度規則,而主線程在子線程全部子線程退出以前都是active狀態。

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

name : 線程的名字,只是一個標識,其能夠重名,getname() 獲取,setname()設置這個名詞

ident:線程ID,其是非0整數,線程啓動後纔會有ID,不然爲None,線程退出,此ID依舊能夠訪問,此ID能夠重複使用
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 ('當前線程name 爲{},ID 爲{}'.format(threading.current_thread().name,threading.current_thread().ident))

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

結果以下

python線程基礎

3 start 和run 的區別與聯繫

1 基本概述

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

run() 運行線程函數

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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()

結果以下

python線程基礎

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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()

結果以下

python線程基礎

結論以下:start 方法的調用會產生新的線程,而run的調用是在主線程中運行的,且run的調用只會調用本身的方法,而start 會調用本身和run方法

2 run 和 start 調用次數問題

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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線程基礎

上述可知,線程在start是會調用start和run屬性運行,且其不能再次啓動線程一次。


調用run方法

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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()

結果以下

python線程基礎

run 方法也只能調用一次

3 start和run 合用

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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()

結果以下

python線程基礎

上述結果代表,run和start的調用不能出如今同一個線程中

4 解決同一代碼中調用問題

從新構建一個新線程並啓動

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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線程基礎

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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()

結果以下

python線程基礎

5 run 和start 的做用

註釋繼承的run方法

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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線程基礎

禁用start方法

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import  time
class MyThread(threading.Thread):  # 自定義一個類,其繼承Thread的相關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線程基礎

結論:start()函數會調用run函數,而run()函數是用來運行函數的,start是建立線程的,在執行start()時run()必不可少,而在運行run()時由於不須要調用start(),所以其是非必須的。


start 會啓用新的線程,其使用能夠造成多線程,而run()是在當前線程中調用函數,不會產生新的線程,其均不能屢次調用

4 多線程概述

一個進程中若是有多個線程,就是多線程,實現一種併發

線程的調度任務是操做系統完成的

沒有開新的線程,這就是普通的函數調用,因此執行完t1.run(),而後執行t2.run(),這不是多線程

當使用start方法啓動線程時,進程內有多個活動的線程並行工做,就是多線程

一個進程中至少有一個線程,做爲程序的入口,這個線程就是主線程,一個進程至少有一個主線程

其餘線程稱爲工做線程

python中的線程沒有優先級的概念

5 線程安全

1 問題

此實例須要在ipython 中運行

python線程基礎

此處的print 會被打斷,其中間有空格,此種狀況稱爲線程不安全。
print 函數的執行分爲兩步:
1 打印字符串
2 換行,就在這之間發生了線程切換,其不安全

2 解決方式:

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

python線程基礎

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

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認是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()

結果以下

python線程基礎

簡單測試的時候使用print,在其餘應用的時候必須使用logging,其是針對日誌打印使用的技術,日誌打印過程當中是不能被中斷的,

6 daemon 線程和 non-daemon線程

1 概述

這裏的daemon線程不是Linux中的守護進程


進程靠線程執行代碼,至少一個主線程,其餘線程是工做線程
主線程是第一個啓動的線程
父線程: 若是線程A中啓動了一個線程B,A就是B的父線程
子線程: B就是A的子線程

在python中,構建線程的時候,能夠設置daemon屬性,這個屬性必須在start方法以前設置好,

相關源碼

python線程基礎

此處代表。若傳入的daemon 不是None,則其表示默認傳入的值,不然,及若不傳入,則表示使用當前線程的daemon

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

活着線程的列表的源碼

python線程基礎

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

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認是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()) #主線程由於其餘線程的執行,所以其處於等待狀態

結果以下

python線程基礎

2 daemon線程

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認是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() #主線程通常會在必定時間內掃描屬性列表,若其中有non-daemon類型
# 的線程,則會等待其執行完成再退出,如果碰見都是daemon類型線程,則直接退出,
print  ('ending')
print (threading.enumerate()) #主線程由於其餘線程的執行,所以其處於等待狀態

結果以下

python線程基礎

上述線程是daemon線程,所以主線程不會等待其完成後再關閉

3 non-daemon 和 damon

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
import time
logging.basicConfig(level=logging.INFO)  #定義基本級別,默認是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線程執行完成後及會直接關閉的狀況

threading.Thread(target=woker,name="work-{}".format(0),daemon=True).start() #主線程通常會在必定時間內掃描屬性列表,若其中有non-daemon類型
# 的線程,則會等待其執行完成再退出,如果碰見都是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() #主線程通常會在必定時間內掃描屬性列表,若其中有non-daemon類型,則不會終止,
# 此處默認從父線程中獲取屬性,父線程中是non-daemon,所以此屬性會一直運行,上面的會關閉,但不會影響這個

print  ('ending')
print (threading.enumerate()) #主線程由於其餘線程的執行,所以其處於等待狀態

結果以下

python線程基礎

結果表示,當non-daemon線程執行完成後,無論damon是否執行完成,主線程將直接終止,不會再次運行。

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
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)  #定義基本級別,默認是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就關閉
    T3=threading.Thread(target=woker1,name="woker{}".format(10),daemon=False)  #此處啓動的線程默認是non-daemon線程,但因爲其父線程是daemon
    # 及就是下面的T1線程,當T2線程執行完畢後線程掃描,發現沒non-daemon線程,則直接退出,此時將不會繼續執行T1 的子線程T3,雖然T3是non-daemon。由於其未啓動
    T3.start()

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

T2=threading.Thread(target=woker1,name="work-{}".format(0)) #主線程通常會在必定時間內掃描屬性列表,若其中有non-daemon類型,則不會終止,
# 此處默認從父線程中獲取屬性,父線程中是non-daemon,所以此屬性會一直運行,上面的會關閉,但不會影響這個
T2.start()
print  ('ending')
print (threading.enumerate()) #主線程由於其餘線程的執行,所以其處於等待狀態

結果以下

python線程基礎

可能孫子線程還沒起來,主線程只看到了daemon線程。則直接進行關閉,

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
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)  #定義基本級別,默認是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就關閉
    T3=threading.Thread(target=woker1,name="woker{}".format(10),daemon=False)  #此處啓動的線程默認是non-daemon線程,但因爲其父線程是daemon
    # 及就是下面的T1線程,當T2線程執行完畢後線程掃描,發現沒non-daemon線程,則直接退出,此時將不會繼續執行T1 的子線程T3,雖然T3是non-daemon。由於其未啓動
    T3.start()

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

結果以下

python線程基礎

也多是孫子線程已經起來了,主線程看到了non-daemon線程,所以未直接關閉,而是等待孫子線程執行完成後才進行關閉操做


相關屬性

daemon 屬性 表示線程是不是daemon線程,這個值必須在start()以前設置,不然會引起異常
isDaemon() 是不是daemon線程
setDaemon() 設置爲daemon線程,必須在start方法以前設置


總結:

python中父線程和子線程沒有直接的管理關係

python主線程是否殺掉線程,看的是daemon,若只有daemon,則直接刪掉全部線程,本身結束,若還有子線程是non-daemon,則會等待

若是想讓一個線程完整執行,則須要定義non-daemon屬性

daemon 屬性,必須在start 以前設置,不然會引起runtimeError異常

線程具備daemon屬性,能夠顯示設置爲True或False,也能夠不設置,則去默認值None
若是不設置daemon,就區當前線程的daemon來設置它

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

從主線程建立的全部線程不設置daemon屬性,則默認都是daemon=False,也就是non-daemon線程

python程序在沒有活着的non-daemon線程運行時推出,也就是剩下的只有daemon線程,主線程才能退出,不然主線程就只能等待。


應用場景:
不關心何時開始,何時結束的時候使用daemon,不然可使用non-daemon

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

daemon和non-daemon 啓動的時候,須要注意啓動的時機。


簡單來講,原本並無daemon thread,爲了簡化程序員工做,讓他們不去記錄和管理那些後臺線程,創造了daemon thread 的概念,這個概念惟一的做用就是,當你把一個線程設置爲daemon時,它會隨着主線程的退出而退出。


主要應用場景:
1 後臺任務,發送心跳包,監控,這種場景較多。
2 主線程工做纔有用的線程,如主線程中維護了公共資源,主線程已經清理了,準備退出,而工做線程使用這些資源工做也沒意義了,一塊兒退出最合適
3 隨時能夠被終止的線程

7 join

join是標準的線程函數之一,其含義是等待,誰調用join,誰等待

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import logging # 導入日誌打印模塊
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()  # 默認狀況下,此線程只能執行少許此,通常不能所有執行
t1.join()  # 經過join方法將本來不能執行完成的線程執行完成了

結果以下

python線程基礎

使用join方法,daemon線程執行完成後,主線程才退出,

join(timeout=None),是線程的標準方法之一。
timeout參數指定調用者等待多久,沒有設置超時,則就一直等到被調用線程結束,調用誰的join方法,就是join誰,誰就要等待。

一個線程中調用另外一個線程的join方法,調用者將被阻塞,直到被調用者線程終止,一個線程能夠被join屢次

若是在一個daemon C 線程中,對另外一個daemon線程D 使用了join方法,只能說明C要等待D,主線程退出,C和D是否結束,也無論他們誰等待誰,都要被殺掉。

join 方法,支持使用等待,但其會致使多線程變成單線程,其會影響正常的運行,所以通常會將生成的線程加入到列表中,進行遍歷獲得對應線程進行計算。

8 threading.local 類

python 提供了threading.local 類,將這個實例化獲得一個全局對象,可是不一樣的線程,這個對象存儲的數據其餘線程看不到

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()

結果以下

python線程基礎

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()

結果以下

python線程基礎

局部變量自己具備隔離效果,一旦變成全局變量,則全部的線程都將可以訪問和修改。

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()

結果以下

python線程基礎

其不一樣線程的TID是不一樣的,可經過不一樣線程的TID進行爲鍵,其結果爲值,即可解決此種亂象

4 threading.local

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
a=threading.local()  # 作到隔離,經過TID進行數據的隔離處理不一樣線程的不一樣數值問題
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()

結果以下

python線程基礎

#!/usr/bin/poython3.6
#conding:utf-8
import  threading
import time
a=threading.local()  # 作到隔離,經過TID進行數據的隔離處理不一樣線程的不一樣數值問題
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()

結果以下

python線程基礎

5 源代碼

python線程基礎
python線程基礎

self.key 是 前面的加上id
經過字典實現,線程ID的地址是惟一的,但跨進程的線程ID 不必定是相同的

進程中的線程地址多是同樣的。每個進程都認爲本身是獨佔資源的,但不必定就是 。

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屬性,所以其不能打印,其是在線程內部,每一個dict對應的值都是獨立的
    print ('end')
threading.Thread(target=work).run()  # 此處是本地線程調用,則不會影響
threading.Thread(target=work).start()

結果以下

python線程基礎

#!/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 #內部線程中定義一個局部變量,則能夠執行和被調用
    print (ctx.x)  #此時的ctx 無此屬性,所以其不能打印,其是在線程內部,
    print ('end')
threading.Thread(target=work).run()  # 此處是本地線程調用,則不會影響
threading.Thread(target=work).start()

結果以下

python線程基礎

7 結論

threading.local類構件了一個大字典,其元素的每一線程實例的地址爲Key和線程的引用線程單獨的字典的映射(棧),經過threading.local 實例就能夠在不一樣的線程中,安全的使用線程獨有的數據,作到了線程間數據的隔離,如同本地變量同樣

8 延遲執行Timter

1 源碼

python線程基礎

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

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("函數執行時間爲{}".format((datetime.datetime.now() - start_time).total_seconds()))

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

結果以下

python線程基礎

此處是延遲執行線程,而不是延遲執行函數,本質上仍是線程

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秒後出結果
t.start()
time.sleep(5) 
t.cancel() #線程被刪除

只要是沒真正執行的線程,都可以被cancel刪除

python線程基礎

#!/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秒後出結果
t.start()
time.sleep(10)
t.cancel()

結果以下
python線程基礎

start方法後,timer對象會處於等待狀態,等待interval以後,開始執行function函數,若是在執行函數以前等待階段,使用了cancel方法,就會跳過執行函數結束。
若是線程已經開始執行了,則cancel就沒有任何效果了

4 總結

Timer是線程Thread的子類,就是線程類,具備線程的能力和特徵它的實例是可以延遲執行目標函數的線程,在真正的執行目標函數以前,均可以cancel它 。

相關文章
相關標籤/搜索