孤荷凌寒自學python第三十八天初識python的執行緒控制
(完整學習過程螢幕記錄視訊地址在文末,手寫筆記在文末)
一、執行緒
在作業系統中存在著很多的可執行的應用程式,每個應用程式啟動後,就可以看著是一個程序,當開啟WINDOWS工作管理員時,在工作管理員的程序選項卡中列出的就是一個一個的程序,基本上每個應用程式都對應著至少一個程序。
在同一程序中,也許同時在做著不止一件事情,比如在向程式介面上顯示資訊和接受資訊的同時,程式也在和遠端伺服器通訊讀取資料,則這兒至少有兩個執行緒執行在同一個程序中。
我的簡單理解是,在同一個作業系統的程序中,可以同時執行多個執行緒的任務。
二、threading模組
threading模組是Python 中主要的執行緒控制模組。
使用threading模組前必須先宣告引用:
import threading
三、threading模組的常見子類與屬性和方法
1
threading.activeCount()
執行此方法返回當前程序下所有活動的執行緒總數。
2
threading.enumerate()
此方法直接返回一個 迭代器,迭代器中包含了,所有當前程序下的所有執行緒的資訊。
在實際測試中,發現在迴圈列印時,總是不打印出第0個執行緒的資訊,沒有核準原因。
見後面的測試程式碼與結果。
3
threading.Thread
這是threading模組下的子模組(類),特別注意Thread模組的首字母是大寫的。這是最容易疏忽之處。
四、獲取或定義得到一個threading.Thread執行緒物件
可以通過以下方式得到threading.Thread執行緒物件
1.第一種:
thread物件=threading.Thread(target=要讓新執行緒執行的函式名 , args=這個函式需要的引數組成的元組)
測試程式碼:
def f1(n):
strtime=str(datetime.now())
print('執行緒' + n + '正在執行中....執行緒啟動於:' +strtime)
t=threading.Thread(target=f1,args=('t1',))
上面程式碼中,我建立了一個執行緒物件 t ,這個執行緒如果啟動,將執行函式f1定義好的內部程式碼塊。
Thread類的建構程式碼(初始化函式)中,要求傳遞的兩個實參,只能按【關鍵字引數】形式傳遞進去。
2.第二種
thread物件=threading.Thread(target=要呼叫的作為函式用的類名(此類的初始化方法需要的引數列表))
測試程式碼:
class myt(object):
def __init__(self,n):
self.n=n
def __call__(self):
strtime=str(datetime.now())
print('執行緒' + self.n + '正在執行中....執行緒啟動於:' +strtime)
t=threading.Thread(target=myt('t1'))
這種方式中,執行緒t啟動後,將執行的是一個可以當著函式來呼叫的類myt.
myt是一個可以被 當著函式來使用的類,因為在類的內部程式碼塊中定義有:__call__()私有方法。
與第一種方式不同的地方在於,這次只向Thread初始化方法傳遞了一個關鍵字實參target。
3.第三種方式
直接寫一個新的繼承自threading.Thread類的子類,然後,用新的類來初始化得到一個thread物件。
測試程式碼:
class myt(threading.Thread):
def __init__(self,n):
threading.Thread.__init__(self)
self.n=n
def run(self):
strtime=str(datetime.now())
print('執行緒' + self.n + '正在執行中....執行緒啟動於:' +strtime)
t=myt('t1')
首先建構了一個繼自threading.Thread的類 myt,這個類的特點有:
(1)在子類的__init__()方法中,必須 先呼叫父類的__init__()方法,在我的上機測試過程中(見過程螢幕錄影,這兒反覆出錯,結果發現就是沒有先呼叫父類的__init()__方法,引發的錯誤。
而原因在測試當時沒有思考出來,所以大家在我的測試螢幕錄影中可以看到沒有想明白,現在思考下,發現可能是因為在threading.Thread類本身的__init__()中要執行非常重要的初始化操作,才能保證thread本類例項化後的物件功能可用,我找到了threading.Thread類本身的__init__()方法的程式碼如下:
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
"""Thisconstructor should always be called with keyword arguments. Arguments are:
*group* should be None; reserved forfuture extension when a ThreadGroup
class is implemented.
*target* is the callable object to beinvoked by the run()
method. Defaults to None, meaningnothing is called.
*name* is the thread name. By default,a unique name is constructed of
the form "Thread-N" where Nis a small decimal number.
*args* is the argument tuple for thetarget invocation. Defaults to ().
*kwargs* is a dictionary of keywordarguments for the target
invocation. Defaults to {}.
If a subclass overrides theconstructor, it must make sure to invoke
the base class constructor (Thread.__init__())before doing anything
else to the thread.
"""
assert group is None, "groupargument must be None for now"
if kwargs is None:
kwargs = {}
self._target= target
self._name =str(name or _newname())
self._args =args
self._kwargs= kwargs
if daemon is not None:
self._daemonic= daemon
else:
self._daemonic= current_thread().daemon
self._ident= None
self._tstate_lock= None
self._started= Event()
self._is_stopped= False
self._initialized= True
# sys.stderr is notstored in the class like
# sys.exc_infosince it can be changed between instances
self._stderr= _sys.stderr
# For debugging and_after_fork()
_dangling.add(self)
以上程式碼可以證明我的猜測是正確的,因此在測試螢幕錄影中我沒有想通的問題現在基本上有答案了,當然正確與否希望大家批評指正。
(2)在定義繼承自threading.Thread類的子類中,__call__()方法 需要被 改名為 run()方法。
五、threading.Thread物件的主要屬性與方法有:
1 name屬性
此屬性可讀寫,用以設定和獲取執行緒物件的名稱。
2 getName()方法
用於獲取執行緒物件的名稱。
3 setName()方法
用於設定執行緒物件的名稱。
4 ident
此屬性是隻讀的,獲取執行緒物件的標識碼,標識碼是數字。
5 is_alive() isAlive()
這兩個方法用於判斷執行緒物件是否正在執行(存活),返回布林物件。
6 join()
這是執行緒物件非常 重要 的方法,此方法有一個可選 形參 timeout
執行執行緒的此方法後,會將呼叫 此執行緒的上級執行緒 阻塞,然後等待此執行緒執行完成,如果為join()方法設定了timeout時間,那麼從此執行緒開始執行起計時,達到timeout指定的時間限額時,將自動解除對上級執行緒的阻塞。
在今天 的測試過程中,沒有著重測試此方法的具體功用,理解可能不完全準確。
7 start()
執行緒執行此方法後,執行緒就開始執行指定任務,同時執行緒的isAlive標識將顯示為True
此外,我沒有發現執行緒有stop()方法,目前也沒有研究到中止指定執行緒執行的其它方法。
測試程式碼一 :
importthreading
from datetimeimport datetime
intcount=3
class myt(object):
def __init__(self,n):
self.n=n
def __call__(self):
strtime=str(datetime.now())
print('執行緒' + self.n + '正在執行中....執行緒啟動於:' +strtime)
def main():
threads=[]
x=range(intcount)
for n in x:
t=threading.Thread(target=myt('t' + str(n)))
#if n==1:
# t.setDaemon(True)
t.name='t' + str(n)
threads.append(t)
for n in x:
threads[n].start()
#print(threading.activeCount())
for item inthreading.enumerate():
print(item)
print('--------')
for item inthreads:
print(item)
print(threads[0].isAlive())
for n in x:
threads[n].join()
if __name__=='__main__':
main()
執行結果:
執行緒t0正在執行中....執行緒啟動於:2018-08-1617:31:08.939618
執行緒t1正在執行中....執行緒啟動於:2018-08-1617:31:08.939618
執行緒t2正在執行中....執行緒啟動於:2018-08-1617:31:08.940647
<_MainThread(MainThread, started 4436)>
<Thread(t1, stopped 2444)>
<Thread(t2, stopped 8676)>
--------
<Thread(t0, stopped 3188)>
<Thread(t1, stopped 2444)>
<Thread(t2, stopped 8676)>
False
測試程式碼二:
importthreading
fromdatetime import datetime
intcount=3
class myt(threading.Thread):
def __init__(self,n):
threading.Thread.__init__(self)
self.n=n
def run(self):
strtime=str(datetime.now())
print('執行緒' + self.n + '正在執行中....執行緒啟動於:' +strtime)
def main():
threads=[]
x=range(intcount)
for n in x:
t=myt('t' + str(n))
#if n==1:
# t.setDaemon(True)
t.name='t' + str(n)
threads.append(t)
for n in x:
threads[n].start()
#print(threading.activeCount())
for item inthreading.enumerate():
print(item)
print('--------')
for item inthreads:
print(item)
print(threads[0].isAlive())
for n in x:
threads[n].join()
if __name__=='__main__':
main()
執行結果:
執行緒t0正在執行中....執行緒啟動於:2018-08-1617:32:36.893226
執行緒t1正在執行中....執行緒啟動於:2018-08-1617:32:36.894223
執行緒t2正在執行中....執行緒啟動於:2018-08-1617:32:36.895221
<_MainThread(MainThread, started 8884)>
<myt(t2, stopped 8940)>
--------
<myt(t0, stopped 7152)>
<myt(t1, stopped 8744)>
<myt(t2, stopped 8940)>
False
注意執行結果中,threading.enumerate()方法返回的迭代器在迴圈取出物件時,總沒有完整列印,且有空行。這點沒有思考出答案。搜尋網路沒有找到答案,還有待繼續思考,並懇請高手予以指點。
——————————
今天整理的學習筆記完成,最後例行說明下我的自學思路:
根據過去多年我自學各種程式語言的經歷,認為只有真正體驗式,解決實際問題式的學習才會有真正的效果,即讓學習實際發生。在2004年的時候我開始在一個鄉村小學自學電腦 並學習vb6程式語言,沒有學習同伴,也沒有高師在上,甚至電腦都是孤島(鄉村那時還沒有網路),有的只是一本舊書,在痛苦的自學摸索中,我找到適應自己零基礎的學習方法:首先是每讀書的一小節就作相應的手寫筆記,第二步就是上機測試每一個筆記內容是否實現,其中會發現書中講的其實有出入或錯誤,第三步就是在上機測試之後,將筆記改為電子版,形成最終的修訂好的正確無誤的學習筆記。
通過反覆嘗試錯誤,在那個沒有分享與交流的黑暗時期我摸黑學會了VB6,爾後接觸了其它語言,也曾聽過付費視訊課程,結果發現也許自己學歷果然太低,就算是零基礎的入門課程,其實也難以跟上進度,講師的教學多數出現對初學者的實際情況並不瞭解的情況,況且學習者的個體也存在差異呢?當然更可怕的是收費課程的價格往往是自己難以承受的。
於是我的所有程式設計學習都改為了自學,繼續自己的三步學習筆記法的學習之路。
當然自學的最大問題是會走那麼多的彎路,沒有導師直接輸入式的教學來得直接,好在網路給我們帶來無限搜尋的機會,大家在網路上的學習日誌帶給我們共享交流的機會,而QQ群等交流平臺、網路社群的成立,我們可以一起自學,互相批評交流,也可以獲得更有效,更自主的自學成果。
於是我以人生已過半的年齡,決定繼續我的程式設計自學之路,開始學習python,只希望與大家共同交流,一個人的獨行是可怕的,只有一群人的共同前進才是有希望的。
誠摯期待您的交流分享批評指點!歡迎聯絡我加入從零開始的自學聯盟。
這個時代網際網路成為了一種基礎設施的存在,於是本來在孤獨學習之路上的我們變得不再孤獨,因為網路就是一個新的客廳,我們時刻都可以進行沙龍活動。
非常樂意能與大家一起交流自己自學心得和發現,更希望大家能夠對我學習過程中的錯誤給予指點——是的,這樣我就能有許多免費的高師了——這也是分享時代,社群時代帶來的好福利,我相信大家會的,是吧!
根據完全共享的精神,開源互助的理念,我的個人自學錄製過程是全部按4K高清視訊錄製的,從手寫筆記到驗證手寫筆記的上機操作過程全程錄製,但因為4K高清檔案太大均超過5G以上,所以無法上傳至網路,如有需要可聯絡我QQ578652607對傳,樂意分享。上傳分享到百度網盤的只是壓縮後的720P的視訊。
我的學習過程錄影百度盤地址分享如下:(清晰度:1280x720)
連結:https://pan.baidu.com/s/1n00vs2zpHiMCCf5eqykGrg
提取碼:bgu2
Bilibili:
https://www.bilibili.com/video/av38088874/
喜馬拉雅語音筆記: