threading 模块支持守护线程, 其工作方式是: 守护线程一般是一个等待客户端请求服务的服务器.
如果把一个线程设置为守护线程, 进程退出时不需要等待这个线程执行完成.
如果主线程准备退出时, 不需要等待某些子线程完成, 就可以为这些子线程设置守护线程标记. 需要在启动线程之前执行如下赋值语句: thread.daemon = True, 检查线程的守护状态也只需要检查这个值即可.
整个 Python 程序将在所有非守护线程退出之后才退出, 换句话说, 就是没有剩下存活的非守护线程时才退出.
使用 thread 模块
以下是三种使用 Thread 类的方法(一般使用第一个或第三个方案)
创建 Thread 的实例, 传给它一个函数.
- import threading
- from time import sleep, ctime
- loops = [3, 2, 1, 1, 1]
- def loop(i, nsec):
- print(f'start loop {i} at: {ctime()}')
- sleep(nsec)
- print(f'end loop {i} at: {ctime()}')
- def main():
- print('start at', ctime())
- threads = []
- nloops = range(len(loops))
- for i in nloops:
- t = threading.Thread(target=loop, args=(i, loops[i]))
- threads.append(t)
- for i in nloops: # start threads
- threads[i].start()
- for i in nloops: # wait for all
- threads[i].join() # threads to finish
- print(f'all done at: {ctime()}')
- if __name__ == '__main__':
- main()
当所有线程都分配完成之后, 通过调用每个线程的 start()方法让它们开始执行, 而不是 在这之前就会执行.
相比于管理一组锁 (分配, 获取, 释放, 检查锁状态等) 而言, 这里只 需要为每个线程调用 join()方法即可.
join()方法将等待线程结束, 或者在提供了超时时间的情况下, 达到超时时间.
使用 join()方法要比等待锁释放的无限循环更加清晰(这也是这种锁 又称为自旋锁的原因).
创建 Thread 的实例, 传给它一个可调用的类实例.
- import threading
- from time import sleep, ctime
- # 创建 Thread 的实例, 传给它一个可调用的类实例
- loops = [3, 2, 1, 1, 1]
- class ThreadFunc(object):
- def __init__(self, func, args, name=''):
- self.name = name
- self.func = func
- self.args = args
- def __call__(self):
- self.func(*self.args)
- def loop(i, nsec):
- print(f'start loop {i} at: {ctime()}')
- sleep(nsec)
- print(f'end loop {i} at: {ctime()}')
- def main():
- print('start at', ctime())
- threads = []
- nloops = range(len(loops))
- for i in nloops:
- t = threading.Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__))
- threads.append(t)
- for i in nloops: # start threads
- threads[i].start()
- for i in nloops: # wait for all
- threads[i].join() # threads to finish
- print(f'all done at: {ctime()}')
- if __name__ == '__main__':
- main()
派生 Thread 的子类, 并创建子类的实例.
- import threading
- from time import sleep, ctime
- # 创建 Thread 的实例, 传给它一个可调用的类实例
- # 子类的构造函数必须先调用其基类的构造函数
- # 特殊方法__call__()在 子类中必须要写为 run()
- loops = [3, 2, 1, 1, 1]
- class MyThread(threading.Thread):
- def __init__(self, func, args, name=''):
- threading.Thread.__init__(self)
- self.name = name
- self.func = func
- self.args = args
- def run(self):
- self.func(*self.args)
- def loop(i, nsec):
- print(f'start loop {i} at: {ctime()}')
- sleep(nsec)
- print(f'end loop {i} at: {ctime()}')
- def main():
- print('start at', ctime())
- threads = []
- nloops = range(len(loops))
- for i in nloops:
- t = MyThread(loop, (i, loops[i]), loop.__name__)
- threads.append(t)
- for i in nloops: # start threads
- threads[i].start()
- for i in nloops: # wait for all
- threads[i].join() # threads to finish
- print(f'all done at: {ctime()}')
- if __name__ == '__main__':
- main()
使用锁
python 和 java 一样, 也具有锁机制, 而且创建与使用锁都是很简便的.
一般在多线程代码中, 总会有一些特 定的函数或代码块不希望 (或不应该) 被多个线程同时执行, 通常包括修改数据库, 更新文件或 其他会产生竞态条件的类似情况
锁有两种状态: 锁定和未锁定. 而且它也只支持两个函数: 获得锁和释放锁.
一般锁的调用如下
- # 加载线程的锁对象
- lock = threading.Lock()
- # 获取锁
- lock.acquire()
- # ... 代码
- # 释放锁
- lock.release()
更简洁的方法是使用 with 关键字, 如下代码功能同上
- # 加载线程的锁对象
- lock = threading.Lock()
- with lock :
- #... 代码
示例代码:
- import threading
- from time import sleep, ctime
- lock = threading.Lock()
- def a():
- lock.acquire()
- for x in range(5):
- print(f'a:{str(x)}')
- sleep(0.01)
- lock.release()
- def b():
- lock.acquire()
- for x in range(5):
- print(f'a:{str(x)}')
- sleep(0.01)
- lock.release()
- threading.Thread(target=a).start()
- threading.Thread(target=b).start()
相关属性和方法
Thread 对象的属性
属性 | 描述 |
---|---|
name | 线程名 |
ident | 线程的标识符 |
daemon | 布尔标志, 表示这个线程是否是守护线程 |
Thread 对象的方法
方法 | 描述 |
---|---|
init (group=None, tatget=None, name=None, args=(), kwargs ={}, verbose=None, daemon=None) | 实例化一个线程对象, 需要有一个可调用的 target, 以及其参数 args 或 kwargs. 还可以传递 name 或 group 参数, 不过后者还未实现. 此 外 , verbose 标 志 也 是 可 接 受 的 . 而 daemon 的 值 将 会 设 定 thread.daemon 属性 / 标志 |
start() | 开始执行该线程 |
run() | 定义线程功能的方法(通常在子类中被应用开发者重写) |
join (timeout=None) | 直至启动的线程终止之前一直挂起; 除非给出了 timeout(秒), 否则 会一直阻塞 |
is_alive() | 布尔标志, 表示这个线程是否还存活 |
threading 模块其他函数
函数 | 描述 |
---|---|
start() | 开始执行该线程 |
active_count() | 当前活动的 Thread 对象个数 |
enumerate() | 返回当前活动的 Thread 对象列表 |
settrace(func) | 为所有线程设置一个 trace 函数 |
setprofile (func) | 为所有线程设置一个 profile 函数 |
stack_size(size=0) | 返回新创建线程的栈大小; 或为后续创建的线程设定栈的大小 为 size |
Lock() | 加载线程的锁对象, 是一个基本的锁对象, 一次只能一个锁定, 其余锁请求, 需等待锁释放后才能获取, 对象有 acquire()和 release()方法 |
RLock() | 多重锁, 在同一线程中可用被多次 acquire. 如果使用 RLock, 那么 acquire 和 release 必须成对出现, 调用了 n 次 acquire 锁请求, 则必须调用 n 次的 release 才能在线程中释放锁对象 |
后记
在 Python 多线程下, 每个线程的执行方式:
1, 获取 GIL
2, 执行代码直到 sleep 或者是 python 虚拟机将其挂起.
3, 释放 GIL
通常来说, 多线程是一个好东西. 不过由于 Python 的 GIL 的限制, 多线程更适合于 I/O 密集型应用(I/O 释放了 GIL, 可以允 许更多的并发), 而不是计算密集型应用. 对于后一种情况而言, 为了实现更好的并行性, 你需要使用多进程, 以便让 CPU 的其他内核来执行.
请注意: 多核多线程比单核多线程更差, 原因是单核下多线程, 每次释放 GIL, 唤醒的那个线程都能获取到 GIL 锁, 所以能够无缝执行, 但多核下, CPU0 释放 GIL 后, 其他 CPU 上的线程都会进行竞争, 但 GIL 可能会马上又被 CPU0 拿到, 导致其他几个 CPU 上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态, 这样会造成线程颠簸(thrashing), 导致效率更低
来源: https://www.cnblogs.com/chenqionghe/p/9668829.html