多线程
简介
什么是线程?
线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。
线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行
一个进程中只有一个主线程,多个子线程
为什么要使用多线程?
线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄 和其他进程应有的状态。
因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率。
线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程 来实现并发比使用多进程的性能高得要多。
进程与线程的区别
特性 | 进程 | 线程 |
---|---|---|
共享的内存空间 | ⛔ | ✔️ |
创建开销 | 大 | 小 |
threading
模块
模块解析
打印所有子模块:
[print(i) for i in dir(threading) if not i.startswith("_")]
子模块名称 | 说明 | 备注 |
---|---|---|
Barrier | ||
BoundedSemaphore | ||
BrokenBarrierError | ||
Condition | ||
Event | ||
ExceptHookArgs | ||
Lock | ||
RLock | ||
Semaphore | ||
TIMEOUT_MAX | ||
Thread | ||
ThreadError | ||
Timer | ||
WeakSet | ||
activeCount | ||
active_count() | 返回当前还活着的线程数目 | 等价于len(threading.enumerate()) |
currentThread | 返回当前正在执行的线程对象,如果当前的线程并不是通过 threading 调用的,那么就返回一个dummy thread | |
current_thread | ||
enumerate | ||
excepthook | ||
functools | ||
get_ident | ||
get_native_id | ||
getprofile | ||
gettrace | ||
local | ||
main_thread | ||
setprofile | ||
settrace | 为每个线程定义一个 settrace/profile 方法,该方法会传递 sys.settrace/profile, 在 run 线程之前被调用。 | |
stack_size | 返回创建一个新线程时所会占用的堆栈大小。指定 size 参数可去修改这个值。 |
Thread 核心模块
线程控制 | 说明 | 备注 |
---|---|---|
start() | start()方法开始线程活动。 对每一个线程对象来说它只能被调用一次,它安排对象在一个另外的单独线程中调- 用 run()方法(而非当前所处线程)。 | 当该方法在同一个线程对象中被调用超过一次时,会引入RuntimeError (运行时错误)。 |
run() | run()方法代表了线程活动的方法,以在子类中重写此方法。 标准 run()方法调用了传递给对象的构造函数的可调对象作为目标参数,如果有这样的参数的话,顺序和关键字参数分别从 args 和kargs 取得。 | |
join([timeout]) | ||
守护进程 | ||
daemon | ||
isDaemon() | ||
setDaemon() | ||
线程状态 | ||
is_alive() | 判断线程还活着没。 | |
sAlive() | 所谓或者就是线程 start 和结束的中间态。 | |
ident | 如果线程没 start,则返回 None,如果线程启动了,则返回一个代表当前线程的魔法数字,常用于从一个字典中获得线程相关的数据。 | |
star() 和 run() :
start()
方法的作用是启动一个新线程,新线程会执行相应的 run()方法,start()不能被重复调用。run()
方法则只是普通的方法调用,在调用线程中顺序运行而已。如果将 start 用 run 代替,你会发现只有一个线程 Main 在一直调用这个 run 方法而已。
join([timeout])
在 python 中,默认情况下(其实就是 setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。
当我们使用setDaemon(True)
方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。那么这时如果你还想让主线程等待子线程结束后才终结,这就要调用到 join 方法啦,当线程调用 join 方法时,它会阻塞住调用它的线程,直到自己任务结束,毋论是正常执行完毕还是因为异常或超时而终结。
timeout 参数:
当设置守护线程时,主线程会等待子线程timeout
的时间后杀死该子线程,最后退出程序。
所以说,如果有 10 个子线程,全部的等待时间就是每个 timeout 的累加和。简单的来说,就是给每个子线程一个 timeout 的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。
没有设置守护线程时,主线程将会等待 timeout 的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
守护线程 daemon & isDaemon() & setDaemon()
守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。
当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
Night gathers, and now my watch begins. It shall not end until my death.
”我的存在就是为了将你守护, 白夜行的既视感。
我们可以通过daemon
或者isDaemon()
去获取线程是否为守护线程,也可通过 setDaemon(True/False)去改变线程的这个属性。
线程名字 name & getname &setName()
错误处理
e. threading.ThreadError
threading的线程异常。
创建线程
方案 1:函数式
以函数形式执行
import threading
def run(n):
print(n)
t1 = theading.Thread(target=run, args=("n",))
t2 = theading.Thread(target=run, args=("n",))
以类形式执行
import threading
方案 2:Thread 子对象(线程对象)
用 Thread 类创建一个线程实例:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
参数解析:
- group:线程组,但因为 python 的多线程不支持 group,这里必须为 None,属于一个扩展保留字段
- target:传入一个可被 run 调用的对象,通常我们会传入一个行数名;
- name:定义线程的名字,否则系统会给该线程定义一个类似 Thread-N 的名字;
- args:tuple 变量集合,传给 target 里的函数对象;
- kwargs:关键字变量字典,依然传给 target 里的函数对象。
- daemon:设置线程是否为守护线程,即是前台执行还是后台执行,默认是非守护线程,当 daemon=True 时,子线程为守护线程,此时主线程不会等待子线程,如果主线程完成会强制杀死所有的子线程然后退出。
可以直接用这个类创建线程实例,传入函数及参数,并调用 start 方法开启进程。
方案 3:创建线程类
import threading
class ThreadLearn(threading.Thread):
def __init__(self, args):
super(ThreadLearn, self).__init__()
self.args = args
def run(self):
print('run....')
pass
# 创建线程
c1 = ThreadLearn(args)
c1.start()
加入暂停, 恢复, 退出功能
class YYS(threading.Thread):
def __init__(self, config: Config):
super(YYS, self).__init__()
# flag 初始化
self.__pause = threading.Event()
self.__running = threading.Event()
self.__pause.set()
self.__running.set()
self.config = config
self.thread_id = threading.current_thread().native_id
def run(self):
if not self.config.thread_id:
self.config.thread_id = self.thread_id
while self.__running.is_set():
# 为True时立即返回,
# 为False时阻塞直到内部的标识位为True后返回
self.__pause.wait()
time.sleep(1)
print(f"【{self.config.thread_id}】yys_client: ", self.config.msg)
else:
print("退出线程: ", self.thread_id)
def pause(self):
# 将__pause标识设置为 False
self.__pause.clear()
def resume(self):
# 将__pause标识设置为 True
self.__pause.set()
def stop(self):
# 将 __所有标识设置为 False
self.__pause.clear()
self.__running.clear()
py2.x
- 函数式使用,通过调用
thread
模块中的start_new_thread()
来产生线程任务 - 类包装多线程对象,
py2.x 函数式多线程
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import thread
import time
# 为线程定义一个函数
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print "%s: %s" % ( threadName, time.ctime(time.time()) )
# 创建两个线程
try:
thread.start_new_thread( print_time, ("Thread-1", 2, ) )
thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print "Error: unable to start thread"
while 1:
pass