python基础语法(15.5)
多线程
初步了解多线程
(个人理解)电脑的cpu一般情况下一次只能给一个进程提供服务,它给每个进程提供服务的时间是相同的,到达时间后就会给下一个进程提供服务,这种行为被称之为时间片轮转。但是由于cpu的速度实在太快了,所以造成看似好像是所有程序一起并发执行。
但是有些时候,有些程序较为耗费资源,一般cpu轮转提供给它的服务不足以支撑它运行。这时候他就会开多天线程,cpu会轮转给每个线程提供单位时间的服务。如此一来就可以让该程序获得更多的资源。
线程是应用程序中工作的最小单位。Python当前的多线程库没有实现优先级,线程组,线程也不能被停止,暂停,恢复,中断。
多线程具有以下有点:
- 使用线程可以把程序中长时间占用资源的任务放到后台处理。
- 可以让显示界面更加灵活,通过点击等事件去触发一些线程,并且可以在线程进行的过程中给用户相应的反馈。
- 提升程序运行速度。
- 一些不需要连续使用系统资源的任务,系统可以在任务间歇将CPU分配给其它进程,更好的利用系统资源。
如何使用多线程
Python的标准库给我们提供了两个标准模块:_thread 和threading。
_thread是低级模块,threading是高级模块,它对 _thread 进行了封装,我们常用threading模块。
threading提供的类
Thread
该类可以按一下两种方式安全的进行子类化:
- 通过将可调用对象传递给构造函数。
- 通过重写子类中的run()方法。
这是一个构造函数,调用它需要传入关键字参数,其中
*group默认为空,为ThreadGroup将来扩展保留的。
*target是run()调用的可调用的对象方法。默认为none,表示不调用任何内容。
*name是线程名称,默认情况下,构造函数的唯一名称,形式为“Thread-N”,其中N是小十进制数。
*args是目标调用的参数元组,默认为()
*kwargs是目标关键字参数的字典调用,默认为{}
如果子类重写构造函数,则必须确保调用积累构造函数(Thread_init_())
Lock
Rlock
Condition
Event
Timer
local
[Bounded]Semaphore
实例方法
-
isAlive():返回线程是否在运行,在启动后,终止前都称之为运行。
-
get/setName(name):获取/设置线程名
-
start()“线程准备就绪,等待CPU调度
-
join([timeout]):阻塞当前的上下文环境的线程,直到调用此方法的线程终止或达到指定的timeout(可选参数)
- 上下文
每个线程都有属于它的一组CPU寄存器,称之为线程的上下文,上下文反应了线程上次运行该线程的CPU寄存器状态。
指令指针和堆栈指针寄存器是线程上下文中两个重要的寄存器,线程总是在进程得到的上下文中运行的,这些地址都用于标注拥有线程的进程地址空间中的内存。
threading模块提供的常量
在threading.TIMEOUT_MAX设置threading全局超时时间。
以函数的形式来开启线程
import threading
import time
#将要执行的方法作为参数传递给Thread的构造方法
def action(arg):
time.sleep(1)
print(threading.current_thread()) # 返回当前的线程对象
print("The arg is:{0}".format(arg))#注意是format不是formate
for i in range(4):
t = threading.Thread(target = action,args = (i,)) # 创建调用action的线程
t.start() # 就绪线程,会自动执行run()来运行线程
if __name__ == "__main__":
print(threading.current_thread())
print("main thread End")
运行结果:
<_MainThread(MainThread, started 24928)>
main thread End
<Thread(Thread-1, started 15260)>
The arg is :0
<Thread(Thread-2, started 18576)>
<Thread(Thread-3, started 19916)>
The arg is :2
The arg is :1
<Thread(Thread-4, started 296)>
The arg is :3
用类来包装线程对象
即自己定义一个类来继承Thread,通过重写run()方法来开启多线程。
代码实例:
class MyThread(threading.Thread):
def __init__(self,arg):
super(MyThread,self).__init__() #调用父类
self.arg = arg #初始化传入参数
def run(self): #重写该方法,定义每个线程要运行的函数
time.sleep(1)
print("The arg is :{0}".format(self.arg))
for i in range(4):
t = MyThread(i)
t.start()
运行结果:
The arg is :0
The arg is :2
The arg is :1
The arg is :3
threading模块提供的常用方法:
- threading.currentThread():返回当前的线程变量。
- threading.enumerate():返回一个包含正在运行的线程的list。(正在运行的线程是指线程启动后,结束前,不包括启动前和终止后的线程。)
- threading.active Count():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
后台线程与前台线程
is/setDeamon(bool):获取/设置是否为后台线程(默认为前台线程(False))。
注: 在start()之前设置
区别:
- 后台线程,主线程结束,后台线程立刻停止。
- 前台线程,主线程结束,等待前台线程执行完程序停止。
相同点:
不论是前台还是后台,在主线程运行时它们也运行。
代码实例:
if __name__ == '__main__':
for i in range(4):
t = MyThread(i)
t.setDaemon(True)
t.start()
print('main thread end!')
运行结果:
main thread end!
如果设置为前台线程,运行结果为:
main thread end!
The arg is :0
The arg is :1
The arg is :3
The arg is :2
join() 线程阻塞
该方法会阻塞当前上下文环境线程,直到调用此方法的线程终止或者到达指定的timeout。即使设置了setDeamon(True)主线程依旧要等待子线程结束。
** 线程必须先start()然后再join()**
代码实例:
if __name__ == '__main__':
th=[]
for i in range(100):
t = MyThread(i)
th.append(t)
t.start()
```
不能直接写成t.join(),这样只会阻塞主线程一次,无法确保再阻塞过程中线程都执行完。
下面的for循环时要阻塞主线程,创建的线程数那么多次,可以保证主线程最后执行完。
这种不同于再上面的for循环里写join(),写在上面会使每个线程不光阻塞主线程,也阻塞接下来的线程。时多线程无意义。
```
for tt in th:
tt.join()
#设置join之后,主线程等待子线程全部执行完成后或者子线程超时后,主线程才结束
print('main thread end!')
网友评论