1、threading模块详解
Python提供的与线程操作相关的模块,网上有很多资料还是用的thread模块,在3.x版本中已经使用
threading来替代thread
,如果你在python 2.x版本想使用threading的话,可以使用dummy_threading模块
。
① threading模块提供的可直接调用的函数
- active_count():获取当前活跃(alive)线程的个数。
- current_thread():获取当前的线程对象。
- get_ident():返回当前线程的索引,一个非零的整数(3.3新增)。
- enumerate():获取当前所有活跃线程的列表。
- main_thread():返回主线程对象(3.4新增)。
- settrace(func):设置一个回调函数,在run()执行之前被调用。
- setprofile(func):设置一个回调函数,在run()执行完毕之后调用。
- stack_size():返回创建新线程时使用的线程堆栈大小。
- threading.TIMEOUT_MAX:堵塞线程时间最大值,超过这个值会栈溢出。
② 线程局部变量(Thread-Local Data)
问题引入:
在一个进程内所有的线程共享进程的全局变量,线程间共享数据很方便但是每个线程都可以随意修改全局变量,可能会引起线程安全问题。
解决方法:
对于这种线程私有数据,最简单的方法就是对变量加锁或使用局部变量,只有线程自身可以访问,其他线程无法访问。除此之外还可以使用threading模块为我们提供的
ThreadLocal变量
,它本身是一个全局变量,但是线程们却可以使用它来保存私有数据。
用法简介:
定义一个全局变量:data = thread.local(),然后就可以往里面存数据啦,比如data.num = xxx,但是有一点要注意:如果data里没有设置对应的属性,直接取会报AttributeError异常,使用时可以捕获这个异常或先调用hasattr(对象,属性)判断对象中是否有该属性!使用代码示例如下:
import threading import random data = threading.local() def show(d): try: num = d.num except AttributeError: print("线程 %s 还未设置该属性!" % threading.current_thread().getName()) else: print("线程 %s 中该属性的值为 = %s" % (threading.current_thread().getName(), num)) def thread_call(d): show(d) d.num = random.randint(1, 100) show(d) if __name__ == '__main__': show(data) data.num = 666 show(data) for i in range(2): t = threading.Thread(target=thread_call, args=(data,), name="Thread " + str(i)) t.start()
运行结果如下:
线程 MainThread 还未设置该属性! 线程 MainThread 中该属性的值为 = 666 线程 Thread 0 还未设置该属性! 线程 Thread 0 中该属性的值为 = 80 线程 Thread 1 还未设置该属性! 线程 Thread 1 中该属性的值为 = 17
不同线程访问这个ThreadLocal变量,返回的都是不一样的值,原理:
threading.local()实例化一个全局对象,这个全局对象里有一个大字典,键值为两个弱引用对象{线程对象,字典对象},然后可以通过
current_thread
()获得当前的线程对象,然后根据这个对象可以拿到对应的字典对象,然后进行参数的读或者写。
③ 线程对象(threading.Thread)
创建新线程的两种方式:
- 1.直接创建threading.Thread对象,并把调用对象作为参数传入;
- 2.继承threading.Thread类,重写run()方法;
使用代码示例(验证单线程快还是多线程快):
import threading import time def catch_fish(): pass def one_thread(): start_time = time.time() for i in range(1, 1001): catch_fish() end_time = time.time() print("单线程测试 耗时 === %s" % str(end_time - start_time)) def muti_thread(): start_time = time.time() for i in range(1, 1001): threading.Thread(target=catch_fish()).start() end_time = time.time() print("多线程测试 耗时 === %s" % str(end_time - start_time)) if __name__ == '__main__': # 单线程 threading.Thread(one_thread()).start() # 多线程 muti_thread()
运行结果如下:
单线程测试 耗时 === 0.00011301040649414062 多线程测试 耗时 === 0.07665514945983887
从输出结果可以看到,多线程反而比单线程要慢,原因是前面介绍过的Python中的全局解释器锁(GIL), 使得任何时候仅有一个线程在执行。
Thread类构造函数
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
构造函数参数依次是:
- group:线程组
- target:要执行的函数
- name:线程名字
- args/kwargs:要传入的函数的参数
- daemon:是否为守护线程
相关属性与函数:
- start():启动线程,只能调用一次
- run():线程执行的操作,可继承Thread重写,参数可从args和kwargs获取;
- join([timeout]):堵塞调用线程,直到被调用线程运行结束或超时;如果
没设置超时时间会一直堵塞到被调用线程结束。
- name/getName():获得线程名;
- setName():设置线程名;
- ident:线程是已经启动,未启动会返回一个非零整数;
- is_alive():判断是否在运行,启动后,终止前;
- daemon/isDaemon():线程是否为守护线程;
- setDaemon():设置线程为守护线程;
④ Lock(指令锁)与RLock(可重入锁)
在概念那里就讲了,多个进程并发的访问临界资源可能会引起线程同步安全问题,写个简单的例子,然后再引入同步锁。代码示例如下:
import threading file_name = "test.txt" # 定义一个写入文件的方法 def write_to_file(msg): try: with open(file_name, "a+", encoding="utf-8") as f: f.write(msg + "\n") except OSError as reason: print(str(reason)) class MyThread(threading.Thread): def __init__(self, msg): super().__init__() self.msg = msg def run(self): write_to_file(self.name + "~" + self.msg) if __name__ == '__main__': for i in range(1, 21): t = MyThread(str(i)).start()
运行结果如下:
# test.txt文件内容 Thread-1~1 Thread-5~5 Thread-3~3 Thread-2~2 Thread-4~4 Thread-6~6 Thread-7~7 Thread-8~8 Thread-10~10 Thread-9~9 Thread-11~11 Thread-13~13 Thread-12~12 Thread-14~14 Thread-15~15 Thread-16~16 Thread-17~17 Thread-19~19 Thread-20~20 Thread-18~18
发现结果并没有按照我们预想的1-20那样顺序打印,而是乱的,threading模块中提供了两个类来确保多线程共享资源的访问:「Lock」 和 「RLock」。