我们要创建Thread对象,然后让他们运行,每个Thread对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
创建Thread对象有两种方法:
1.直接创建Thread,将一个callable对象从类的构造器传递出去,这个callable就是回调函数,用来处理任务。
2.编写一个自定义类继承Thread,然后复写run()方法,在ru()方法中编写任务处理代码,然后创建Thread的子类。
# 导入Python标准库中的Thread模块
from threading import Thread
# 创建一个线程
mthread = threading.Thread(target=function_name, args=(function_parameter1, function_parameterN))
# 启动刚刚创建的线程
mthread .start()
参数说明:
function_name: 需要线程去执行的方法名
args: 线程执行方法接收的参数,该属性是一个元组,如果只有一个参数也需要在末尾加逗号
Thread类定义了以下常用方法与属性:
方法与属性 | 说明 |
---|---|
方法与属性 | 说明 |
start() | 启动线程,等待CPU调度 |
run() | 线程被cpu调度后自动执行的方法 |
getName()、setName()和name | 用于获取和设置线程的名称 |
setDaemon() | 设置为后台线程或前台线程(默认是False,前台线程)。如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程执行完成后,程序才停止 |
ident | 获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None |
is_alive() | 判断线程是否是激活的(alive)。从调用start()方法启动线程,到run()方法执行完毕或遇到未处理异常而中断这段时间内,线程是激活的 |
isDaemon()方法和daemon属性 | 是否为守护线程 |
join([timeout]) | 调用该方法将会使主调线程堵塞,直到被调用线程运行结束或超时。参数timeout是一个数值类型,表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程结束 |
1)创建多线程
Thread的构造方法中,最重要的参数是target,所以我们需要将一个callable对象赋值给它,线程才能正常运行。
如果要让一个Thread对象启动,调用它的start()方法即可。
import threading
import time
def test():
for i in range(5):
print('The {} child thile '.format(i))
time.sleep(1)
thread = threading.Thread(target=test)
thread.start()
# thread.join()
for i in range(5):
print('The {} main thile '.format(i))
time.sleep(1)
上面代码很简单,在主线程上打印 5 次,在一个子线程上打印 5 次。
The 0 child thile The 0 main thile
The 1 child thile The 1 main thile
The 2 child thile
The 2 main thile
The 3 main thile The 3 child thile
The 4 child thile
The 4 main thile
上面的 callable 没有参数,如果需要传递参数的话,args 是固定参数,kwargs 是可变参数。
2)Thread的名字
每一个Thread都有一个name的属性,代表的就是线程的名字,这个可以在构造方法中赋值。如果在构造方法中没有为name赋值的话,默认就是“Thread-N”的形式,N是数字。
import threading
import time
def test():
for i in range(5):
print(threading.current_thread().name+" test ",i)
time.sleep(1)
thread=threading.Thread(target=test)
thread.start()
for i in range(5):
print("This is "+threading.current_thread().name+"main ",i)
time.sleep(1)
输出为:
Thread-1 test This is MainThreadmain 00
Thread-1 test 1
This is MainThreadmain 1
Thread-1 test 2
This is MainThreadmain 2
This is MainThreadmain 3Thread-1 test
3
Thread-1 test 4
This is MainThreadmain 4
在Thread对象创建时,给name赋值
thread = threading.Thread(target=test,name='aoligei')
运行结果为:
aoligei test This is MainThreadmain 00
This is MainThreadmain 1
aoligei test 1
This is MainThreadmain 2
aoligei test 2
This is MainThreadmain 3
aoligei test 3
This is MainThreadmain 4
aoligei test 4
3)Thread的生命周期
1.创建对象时,代表Thread内部被初始化
2.调用start()方法后,thread会开始运行
3.thread代码正常运行结束或者遇到异常,线程会终止。
可以通过Thread的is_alive()方法查询线程是否还在运行。is_alive() 返回 True 的情况是 Thread 对象被正常初始化,start() 方法被调用,然后线程的代码还在正常运行。
import threading
import time
def test():
for i in range(5):
print(threading.current_thread().name+' test ',i,"*"*10)
time.sleep(0.5)
thread = threading.Thread(target=test,name='aoligei')
thread.start()
for i in range(5):
print(threading.current_thread().name+' main ', i)
print(thread.name+' is alive ', thread.is_alive())
time.sleep(1)
设置子线程的睡眠时间更短,可以让它早点结束
aoligei test MainThread main 00
aoligei is alive **********
True
aoligei test 1 **********
MainThread main 1
aoligei is alive True
aoligei test 2 **********
aoligei test 3 **********
MainThread main 2
aoligei is alive True
aoligei test 4 **********
MainThread main 3
aoligei is alive False
MainThread main 4
aoligei is alive False
4)join()方法提供阻塞手段
import threading
import time
def test():
for i in range(5):
print(threading.current_thread().name+' test ',i,"*"*10)
time.sleep(2)
thread = threading.Thread(target=test,name='aoligei')
thread.start()
thread.join()
for i in range(5):
print(threading.current_thread().name+' main ', i)
print(thread.name+' is alive ', thread.is_alive())
time.sleep(1)
调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程。主线程创建了 aoligei 对象后,让其 start,然后通过调用 join() 方法,实现等待。程序运行结果如下:
aoligei test 0 **********
aoligei test 1 **********
aoligei test 2 **********
aoligei test 3 **********
aoligei test 4 **********
MainThread main 0
aoligei is alive False
MainThread main 1
aoligei is alive False
MainThread main 2
aoligei is alive False
MainThread main 3
aoligei is alive False
MainThread main 4
aoligei is alive False
默认情况下,join()会一直等待对应线程的结束,但可以通过参数赋值,等待规定的时间即可。
def join(self.timeout=None):
timeout是一个浮点参数,单位是秒。
5)守护线程
import threading
import time
def test():
for i in range(5):
print(threading.current_thread().name+' test ',i,"*"*10)
time.sleep(2)
thread = threading.Thread(target=test,name='aoligei')
thread.start()
for i in range(5):
print(threading.current_thread().name+' main ', i)
print(thread.name+' is alive ', thread.is_alive())
time.sleep(1)
输出为:
aoligei test MainThread main 00
**********aoligei is alive
True
MainThread main 1
aoligei is alive True
aoligei test 1 **********
MainThread main 2
aoligei is alive True
MainThread main 3
aoligei is alive True
aoligei test 2 **********
MainThread main 4
aoligei is alive True
aoligei test 3 **********
aoligei test 4 **********
MainThread结束运行后,aoligei仍然在运行,这是因为MainThread在等待其他线程的结束。
aoligei的daemon属性默认为False,这使得 MainThread 需要等待它的结束,自身才结束。
如果要达到守护线程(MainThread 结束,子线程也立马结束),怎么做呢? 其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。 当然也可以在子线程的构造器中传递 daemon 的值为 True。
thread = threading.Thread(target=test,name='TestThread',daemon=True)
# thread.setDaemon(True)
修改之后运行结果为:
aoligei test MainThread main 00
aoligei is alive **********
True
MainThread main 1
aoligei is alive True
aoligei test 1 **********
MainThread main 2
aoligei is alive True
MainThread main 3
aoligei is alive True
aoligei test 2 **********
MainThread main 4
aoligei is alive True
6)线程数量
threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
def enumerate():
“”"Return a list of all Thread objects currently alive.
The list includes daemonic threads, dummy thread objects created by current_thread(), and the main thread. It excludes terminated threads and threads that have not yet been started.
import threading
import time
def func1():
print("This is func1")
time.sleep(1)
def func2():
print("This is func2")
time.sleep(1)
for i in range(4):
t1=threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t2.start()
t1.start()
le=len(threading.enumerate())
print("当前线程数为",le)
运行结果为:
This is func2
This is func1当前线程数为
3
This is func2
This is func1当前线程数为
5
This is func2
This is func1当前线程数为
7
This is func2
This is func1当前线程数为
9
7)其他的方法
threading模块提供了一些比较实用的方法或者属性:
方法与属性 | 描述 |
---|---|
current_thread() | 返回当前线程 |
active_count() | 返回当前活跃的线程数,1个主线程+n个子线程 |
get_ident() | 返回当前线程 |
enumerate() | 返回当前活动 Thread 对象列表 |
main_thread() | 返回主 Thread 对象 |
settrace(func) | 为所有线程设置一个 trace 函数 |
setprofile(func) | 为所有线程设置一个 profile 函数 |
stack_size([size]) | 返回新创建线程栈大小;或为后续创建的线程设定栈大小为 size |
TIMEOUT_MAX | Lock.acquire(), RLock.acquire(), Condition.wait() 允许的最大超时时间 |
自定义一个Thread的子类,然后复写它的run()方法
import threading
import time
class TestThread(threading.Thread):
def __init__(self,name=None):
threading.Thread.__init__(self,name=name)
def run(self):
for i in range(5):
print(threading.current_thread().name+" test ",i)
time.sleep(1)
thread=TestThread(name='aoligei')
thread.start()
for i in range(5):
print(threading.current_thread().name+" main ",i)
time.sleep(1)
自定义了 TestThread 这个类,然后继承threading.Thread 只有在 run() 方法中处理逻辑,最终运行结果如下:
aoligei test MainThread main 00
aoligei test MainThread main 1
1
MainThread main 2
aoligei test 2
aoligei test 3
MainThread main 3
MainThread main 4
aoligei test 4
3.GIL
全局解释器锁(global interpreter lock),每个线程在执行时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU,也就是说多线程并不是真正意义上的同时执行。
Python的标准实现是CPython。
CPython执行Python代码分为2个步骤:首先,将文本源码解释编译为字节码,然后再用一个解释器去 解释运行字节码。字节码解释器是有状态的,为了维护该状态的一致性,因此使用了GIL(Global Interpreter Lock,全局解释器锁)。 GIL的存在,使得CPython在执行多线程代码的时候,同一时刻只有一个线程在运行,无法利用多CPU 提高运算效率。但是这个特点也带来了一个好处:CPython运行多线程的时候,内部对象缺省就是线程 安全的。这个特性,被非常多的Python库开发者所依赖,直到CPython的开发者想要去除GIL的时候, 发现已经有大量的代码库重度依赖这个GIL带来的内部对象缺省就是线程安全的特性,变成一个无法解 决的问题了。
虽然多线程在并行计算场景下无法带来好处,但是在阻塞式IO场景下,却仍然可以起到提高效率的作 用。这是因为阻塞式IO场景下,线程在执行IO操作时并不需要占用CPU时间,此时阻塞IO的线程可以被 挂起的同时继续执行IO操作,而让出CPU时间给其他线程执行非IO操作。这样一来,多线程并行IO操作 就可以起到提高运行效率的作用了。
综上,Python的标准实现CPython,由于GIL的存在,同一个时刻只能运行一个线程,无法充分利用多 CPU提升运算效率,因此Python的多线程适用于阻塞式IO的场景,不适用于并行计算的场景
4.多进程和多线程对比
引自菜鸟教程:参考链接
如果多线程的进程是CPU密集型的,那多线程并不能有多少效率上的提升,相反还可能会因为线程的频繁切换,导致效率下降,推荐使用多进程;
如果是IO密集型,多线程进程可以利用IO阻塞等待时的空闲时间执行其他线程,提升效率。所以我们根据实验对比不同场景的效率
多线程在IO密集型的操作下似乎也没有很大的优势(也许IO操作的任务再繁重一些就能体现出优势),在CPU密集型的操作下明显地比单线程线性执行性能更差,但是对于网络请求这种忙等阻塞线程的操作,多线程的优势便非常显著了
多进程无论是在CPU密集型还是IO密集型以及网络请求密集型(经常发生线程阻塞的操作)中,都能体现出性能的优势。不过在类似网络请求密集型的操作上,与多线程相差无几,但却更占用CPU等资源,所以对于这种情况下,我们可以选择多线程来执行