孤荷凌寒自学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/
喜马拉雅语音笔记:
网友评论