什么是线程? 同进程下执行,并共享相同的上下文 线程间的信息共享和通信更加容易 多线程并发执行 需要同步原语
python多线程
_thread提供了基本的线程和锁
threading提供了更高级别的,功能更全面的线程管理: 支持同步机制 支付守护线程
thread模块的函数: start_new_thread(funtion,args,kwargs(None)) allocate_lock() 分配lockType锁对象 exit() 线程退出指令 acquire(wait=None) 尝试获取锁对象 locked() 如果获得锁对象返回True,否则返回False release() 释放锁 run() 使用者重写方法 join(timeout=None) 线程结束之前一直挂起,除非给出超时时间,否则会一直阻塞 getName() 返回线程名 setName(name) 设定线程名 isAlive/is_alive() 线程是否还存活 setDaemon(daemonic) 把线程的守护标志设定为布尔值daemonic,必需在线程start()之前调用(Daemon 译为后台程序)
举个例子:
import logging
from time import sleep, ctime
logging.basicConfig(level=logging.INFO)
# 写两个线程,分别执行
def test0():
logging.info('开始test0,时间为' + ctime())
sleep(2)
logging.info('结束test0,时间为' + ctime())
def test1():
logging.info('开始test1,时间为' + ctime())
sleep(4)
logging.info('结束test1,时间为' + ctime())
def main():
logging.info('开启所有线程,时间为' + ctime())
test0()
test1()
logging.info('结束所有进程,时间为' + ctime())
if __name__=='__main__':
main()
"""运行结果为:
INFO:root:开启所有线程,时间为Sat Oct 29 22:54:28 2022
INFO:root:开始test0,时间为Sat Oct 29 22:54:28 2022
INFO:root:结束test0,时间为Sat Oct 29 22:54:30 2022
INFO:root:开始test1,时间为Sat Oct 29 22:54:30 2022
INFO:root:结束test1,时间为Sat Oct 29 22:54:34 2022
INFO:root:结束所有进程,时间为Sat Oct 29 22:54:34 2022
"""
如何使用_thread模块,代码如下:
import _thread
import logging
from time import sleep, ctime
logging.basicConfig(level=logging.INFO)
def test0():
logging.info('开始test0,时间为' + ctime())
sleep(2)
logging.info('结束test0,时间为' + ctime())
def test1():
logging.info('开始test1,时间为' + ctime())
sleep(4)
logging.info('结束test1,时间为' + ctime())
def main():
logging.info('开启所有线程,时间为' + ctime())
_thread.start_new_thread(test0, ()) # 这里传入的是函数,不要加括号
_thread.start_new_thread(test1, ()) # 第二个参数是test1函数的参数,这里为空
sleep(6)
logging.info('结束所有进程,时间为' + ctime())
if __name__ == '__main__':
main()
为什么一定要在main里面sleep(6)呢?
这就是_thread的弊端,如果不加sleep,主线程退出后,所有子线程都会被强行退出。它不会守护线程。但问题是我们不知道子线程需要运行多长时间。要解决这个问题需要用到lock锁,不过有点复杂,先不管。
换个思路,咱们来使用threading,它自带的join方法,可以在线程没结束时进行阻塞。
最终代码:
import threading
import logging
from time import sleep, ctime
logging.basicConfig(level=logging.INFO)
# 写一个类去继承
class MyThread(threading.Thread):
def __init__(self, func, args, func_name=''):
"""
:param func: 函数
:param args: 函数的参数
:param func_name: 函数名(举例子)
"""
threading.Thread.__init__(self) # 主动去调用init方法
self.args = args
self.func = func
self.func_name = func_name
def run(self): # 重写threading.Thread中的run方法
self.func(*self.args)
tests = [3, 3]
def test(n, sec):
"""
:param n: 线程数
:param sec: 运行时间
"""
logging.info('开始线程' + str(n) + ',时间为' + ctime())
sleep(sec)
logging.info('结束线程' + str(n) + ',时间为' + ctime())
def main():
logging.info('开启所有线程,时间为' + ctime())
threads = []
ns = range(len(tests))
for i in ns:
t = MyThread(test, (i, tests[i]), test.__name__)
threads.append(t)
for i in ns:
threads[i].start()
for i in ns:
threads[i].join() # threading中自带的join方法,如果线程没结束则会阻塞
logging.info('结束所有进程,时间为' + ctime())
if __name__ == '__main__':
main()
运行结果为:
INFO:root:开启所有线程,时间为Sat Oct 29 23:52:28 2022
INFO:root:开始线程0,时间为Sat Oct 29 23:52:28 2022
INFO:root:开始线程1,时间为Sat Oct 29 23:52:28 2022
INFO:root:结束线程1,时间为Sat Oct 29 23:52:31 2022
INFO:root:结束线程0,时间为Sat Oct 29 23:52:31 2022
INFO:root:结束所有进程,时间为Sat Oct 29 23:52:31 2022
需要注意:线程1和2不一定哪个先结束。
多线程的应用场景:
比如app自动化测试中可能需要用到日志查询,日志输出是一个持续的过程,如果在操作结束后去抓日志,有可能抓不到关键信息,此时使用多线程,在操作之前就开始抓日志,一直持续到操作结束后的查询阶段,查询结束后再退出线程。确保能抓到关键信息。