一、线程(重点)
- python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用。
- 线程执行的顺序不确定。
- 如果创建Thread时执行的函数,运行结束那么意味着:这个子线程结束了....
- 如果主线程提前结束,子线程也会结束(不论子线程是否执行完)。
- 当调用Thread的时候,不会创建线程。当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及让这个线程开始执行。
- 线程结束,函数结束。
- 主线程最后结束。
1.1 使用threading模块
1.1.1 单线程执行
#coding=utf-8
import time
def saySorry():
print("亲爱的,我错了,我能吃饭了吗?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
saySorry()
1.1.2 多线程执行
#coding=utf-8
import threading
import time
# 定义函数不会执行,调用函数才执行
def saySorry():
print("亲爱的,我错了,我能吃饭了吗?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=saySorry)
t.start() #启动线程,即让线程开始执行
说明:
- 可以明显看出使用了多线程并发的操作,花费时间要短很多
- 当调用
start()
时,才会真正的创建线程,并且开始执行
1.2 主线程会等待所有的子线程结束后才结束
#coding=utf-8
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d" % i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d" % i)
sleep(1)
if __name__ == '__main__':
print('---开始---:%s' % ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start() # 创建了第1个子线程,主线程继续往下走,第1个子线程调用函数执行。
t2.start() # 创建了第2个子线程,主线程继续往下走,第2个子线程调用函数执行。
sleep(5) # 屏蔽此行代码,试试看,程序是否会立马结束?
print('---结束---:%s' % ctime()) # ---结束---:Mon Aug 1 21:16:22 2022
1.3 查看线程数量 threading.enumerate()
#coding=utf-8
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
sleep(1)
if __name__ == '__main__':
print('---开始---:%s' % ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate()) # 返回值threading.enumerate()列表有几个元素,就有几个线程
print('当前运行的线程数为:%d' % length)
if length<=1: # 只剩一个的时候,一定是主线程,执行完毕退出
break
sleep(0.5)
# threading.enumerate() 列表中有3个元组
# [<_MainThread(MainThread, started 27000)>, <Thread(Thread-1, started 45768)>, <Thread(Thread-2, started 61320)>]
1.3.1 补充-拆包
names = ["aa", "bb", "cc"]
for temp in names:
print(temp)
结果:
aa
bb
cc
names = ["aa", "bb", "cc"]
for temp in enumerate(names):
print(temp)
结果:
(0, 'aa')
(1, 'bb')
(2, 'cc')
names = ["aa", "bb", "cc"]
for i, name in enumerate(names):
print(i, name)
结果:
0 aa
1 bb
2 cc
二、线程-类
2.1 线程执行代码的封装(类)
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread
就可以了,然后重写run
方法
示例如下:
#coding=utf-8
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) # name属性中保存的是当前线程的名字
print(msg)
self.login()
self.register()
def login(self):
print("这是登录···")
def register(self):
print("这是注册代码")
if __name__ == '__main__':
t = MyThread() # 创建一个实例对象,这个对象只能创建一个线程
t.start() # 一个线程只能执行一个函数,start会调用run方法
结果:
# I'm Thread-1 @ 0
# 这是登录···
# 这是注册代码
# I'm Thread-1 @ 1
# 这是登录···
# 这是注册代码
# I'm Thread-1 @ 2
# 这是登录···
# 这是注册代码
说明
- python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
2.2 线程的执行顺序
#coding=utf-8
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i)
print(msg)
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
执行结果:(运行的结果可能不一样,但是大体是一致的)
I'm Thread-1 @ 0
I'm Thread-2 @ 0
I'm Thread-5 @ 0
I'm Thread-3 @ 0
I'm Thread-4 @ 0
I'm Thread-3 @ 1
I'm Thread-4 @ 1
I'm Thread-5 @ 1
I'm Thread-1 @ 1
I'm Thread-2 @ 1
I'm Thread-4 @ 2
I'm Thread-5 @ 2
I'm Thread-2 @ 2
I'm Thread-1 @ 2
I'm Thread-3 @ 2
说明:
- 从代码和执行结果我们可以看出,多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。
- 上面的代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
2.3 总结
- 每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
- 当线程的run()方法结束时该线程完成。
- 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。