多线程基本概念
一个进程中,有多个工作线程,是为多线程。
主线程一般是做资源管理,协调,监视等功能,而工作线程是真正运行工作函数,真正干活的。
每个线程都有自己的stack栈,栈是一种后进先出内存数据结构。每次执行函数,都要将函数压到栈中。所以多线程中,如果调用同一个函数,这个函数会被分别压到各个线程的栈中,互不干扰。
每个线程调用函数,都是在自己的栈中调用的,栈是线程私有的。
线程的数据也是保存在线程栈中的。
注意:
如果主线程创建了工作线程,而工作线程中有创建了子子工作线程,此时的子线程是不需要对子子工作线程负责的。也就是子线程运行完成后,可直接退出,而不关心其子线程是否还在运行。
线程安全
通过多线程执行一段代码,如果不会产生不确定结果,那么这段代码就是线程安全的。
多线程在执行过程中,由于共享同一进程中的数据,多个线程使用同一个数据,那么就有可能被相互修改,从而导致某时刻无法确定这个数据的值,最终的运行结果将不可预期,这就是线程不安全。
daemon线程
Python中,构造线程的时候,可以设置daemon属性,这个属性必须在start方法前设置好。
daemon是一个布尔值,在线程源码中,使用self._daemonic 记录daemon值。
当前线程默认是False,如果工作线程没有设置daemon,则使用当前线程的 False。
源码如下:
在初始化函数中,传入的daemon默认是None
继续向下看,如果没有设置 daemon 则使用 current_thread() 的daemon
如果线程的daemon是True,则这个线程是daemon线程,否则是non-daemon线程。
定义MainThread也就是主线程时就已经写死了 daemon是False
总结:
- 线程具有一个daemon属性,可以手动设置True 或 False
- 如果不设置,则使用 主线程的daemon值,默认为False
名称 | 含义 |
daemon属 性 | 表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发 RuntimeError异常 |
isDaemon() | 是否是daemon线程 |
setDaemon | 设置为daemon线程,必须在start方法之前设置 |
daemon属性的作用:
多线程环境中,在主线程执行完成后,如果剩余的工作线程是daemon线程,则主线程不会关心daemon线程是否执行完毕,都会结束掉daemon线程。
如果剩余线程是non-daemon,则主线程会等待non-daemon线程执行完成后,再退出。
import threading import time def worker(timeout): print('in the', threading.current_thread().name, threading.current_thread().ident) time.sleep(timeout) print('{}: finished ~~~~~~~~~~~~~~~~'.format(threading.current_thread().name)) t1 = threading.Thread(target=worker, name='worker1', args=(10, )) # 不设置daemon,则使用 主线程的daemon,是False t2 = threading.Thread(target=worker, name='worker2', daemon=True, args=(10, )) t3 = threading.Thread(target=worker, name='worker3', daemon=False, args=(5, )) t1.setDaemon(True) # 可通过setDaemon()方法设置daemon值,必须在线程执行前设置。 t1.start() t2.start() t3.start() print('*' * 30)
主线程退出时,并不会关心 daemon 线程是否执行完,都会将其结束。
主线程要等待所有的non-daemon线程执行完成后,才能退出。
简单来说, 就是默认情况下, 主线程执行完毕, 但是其子线程还没有执行完, 这时候主线程必须等待子线程运行结束, 才能结束。
如果这个子线程无关紧要就将其daemon=True, 这样主线程执行完, 它就会被强制结束。
daemon的应用场景
主要应用场景有:
1. 后台任务。如发送心跳包、监控,这种场景最多
2. 主线程工作才有用的线程。如主线程中维护这公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适
3. 随时可以被终止的线程
如果主线程退出,想所有其它工作线程一起退出,就使用daemon=True来创建工作线程。
比如,开启一个线程定时判断WEB服务是否正常工作,主线程退出,工作线程也没有必须存在了,应该
随着主线程退出一起退出。这种daemon线程一旦创建,就可以忘记它了,只用关心主线程什么时候退
出就行了。
daemon线程,简化了程序员手动关闭线程的工作。
join() 方法-父线程阻塞
线程使用join方法后,主线程会阻塞一定时间,不会向下执行。
这段时间内,如果工作线程执行完成后,主线程继续运行。如果时间到了, 工作线程还没有执行完,主线程也会继续执行。
join(timeout), 指定阻塞时间,缺省是None,代表永久阻塞。
import threading import time def worker(timeout): print('in the', threading.current_thread().name, threading.current_thread().ident) time.sleep(timeout) print('finished ~~~~~~~~~~~~~~') t1 = threading.Thread(target=worker, name='worker1', daemon=True, args=(10,)) t1.start() t1.join() # 使用join方法,将t1线程加入到主线程中去,进行阻塞。哪个线程启动它,就加入到哪里去 print('*' * 30)
主线程要等到 工作线程执行完成,即使工作线程是daemon线程。
可以向 join() 函数中传入阻塞时间单位是s,如果在阻塞时间内,工作线程依然没有执行完,主线程继续执行,主线程执行完后,如果还有工作线程,python进程会检查工作线程是否是daemon,daemon线程则会将其结束,退出进程。如果是non-daemon线程,则需要等待其执行完毕才可退出进程。
threading.local类
# encoding = utf-8 __author__ = "mcabana.com" import threading class A: def __init__(self): self.x = 100 a = A() y = 10 print(a.x) def worker(): print('in the', threading.current_thread().name, threading.current_thread().ident, y, a.x) a.x += 1 print(a.x) # 在线程内,可以修改类属性 t1 = threading.Thread(target=worker, name='worker1') t1.start() t1.join() print('*' * 30) print(a.x)
通过上面的代码可以发现,在线程内是可以调用并修改过线程外实例的属性的,但是在多线程环境下这种修改是极不安全的,如下:
# encoding = utf-8 __author__ = "mcabana.com" import threading class A: def __init__(self): self.x = 100 a = A() y = 10 print(a.x) def worker(): global y for c in range(1000000): a.x += 1 print('in the', threading.current_thread().name, a.x) # a.x += 1 # print(a.x) # 在线程内,可以修改类属性 for i in range(5): threading.Thread(target=worker).start() print('*' * 30, a.x)
可以看到在计算量大的情况下,a.x 的值是无法预测的,并且每次都是不一样的,这就是前面说的,多个线程使用同一个数据,无法保证线程安全。同样的使用全局变量也会出现这个问题。
只有使用函数变量,才可以说是绝对安全的,因为函数变量是函数内有效,每个线程都会压栈,每个栈都是独立的。
关于threading.local()
# encoding = utf-8 __author__ = "mcabana.com" import threading a = threading.local() print(a) a.x = 100 def worker(): print(a) print(hasattr(a, 'x')) print(threading.current_thread().name, a.x) t1 = threading.Thread(target=worker, name='worker1') t1.start() print('*' * 30, a.x)
通过threading.local() 实例化的对象,在线程内是无法访问其属性的。
在当前线程中,为threading.local()实例的属性创建的新值,只对当前线程有关,如果不是therading.local的实例,则不影响。
# encoding = utf-8 __author__ = "mcabana.com" import threading a = threading.local() print(a) a.x = 100 def worker(): a.y = 10 print('in the worker', hasattr(a, 'x'), hasattr(a, 'y')) # False, True print('in the worker', threading.current_thread().name, a.y) t1 = threading.Thread(target=worker, name='worker1') t1.start() t1.join() print('*', a.x) # 100 print('*', hasattr(a, 'y')) # False
threading.local 的属性访问与赋值,只与当前线程有关。
https://www.hugbg.com/archives/2777.html
评论