1、threading.Thread
threading.Thread(target=None, name=None, args=(), kwargs={}, *, daemon=None)
target:在线程中调用的对象,可以为函数或者方法;
args,kwargs均为target对象的参数。
daemon:是否设置为守护对象,默认为否。若将一个线程设置为守护线程,则只有守护线程结束python程序才会退出。如果程序在结束时,还有守护线程未进行完,则会强退该进程;而非守护对象只有在自身执行完毕之后才会退出。典型的例子是,主线程末尾开启了一个线程,若为守护线程,开启后会马上在主线程执行结束时被强制退出;若为非守护进程,则会一直等到该进程执行完之后才会退出。
"""
通过实例化threading.Thread类创建线程
"""
import time
import threading
def test_thread(para='hi', sleep=3):
"""线程运行函数"""
time.sleep(sleep)
print(para)
def main():
# 创建线程
thread_hi = threading.Thread(target=test_thread)
thread_hello = threading.Thread(target=test_thread, args=('hello', 5), daemon=True)
# 启动线程
thread_hi.start()
thread_hello.start()
print('Main thread has ended!')
if __name__ == '__main__':
main()
输出:
(当thread_hello的sleep参数为1)
Main thread has ended!
hello
hi
(当thread_hello的sleep参数为5,在thread_hi执行完之后程序直接结束)
Main thread has ended!
hi
Thread的方法:
start():开启线程,如果线程是通过继承threading.Thread子类的方法定义的,则调用该类中的run()方法;start()只能调用一次,否则报RuntimeError。
join(timeout=None):让当前线程阻塞(一般就是指主线程)等待直到调用join方法的线程结束,timeout参数可以用于设置超时时间。可以使用is_alive()方法来判断线程是否存活(即在run()方法执行的过程中返回True)。join方法可被多次调用,但在线程中调用自己的join方法或者在start()前调用join()会报RuntimeError。
2、threading.lock
使用threading.lock创建线程锁,仅有锁定和非锁定两种状态。锁被创建时,是非锁定状态。
在某一个线程中调用lock.acquire(timeout)方法时,会阻塞其它尝试获取锁的线程(因此在需要确保线程按目标顺序执行时,在这几个线程中调用锁),timeout默认为-1,即直到锁被释放之前始终阻塞其它获取锁的线程,使用其它值则会最多阻塞该时长。
在线程末尾调用lock.release()方法可以释放锁,将其状态改为非锁定,其它尝试获取锁的进程不再阻塞。当然,也可以不在调用锁的进程中释放锁,其它位置均可使用release()方法。在lock非锁定状态下调用release()会导致RuntimeError错误。
"""
使用锁实现线程同步
"""
import time
import threading
# 创建锁
lock = threading.Lock()
# 全局变量
global_resource = [None] * 5
def change_resource(para, sleep):
# 请求锁
lock.acquire()
# 这段代码如果不加锁,第一个线程运行结束后global_resource中是乱的,
# 输出为:修改全局变量为: ['hello', 'hi', 'hi', 'hello', 'hello']
# 第二个线程运行结束后,global_resource中还是乱的,
# 输出为:修改全局变量为: ['hello', 'hi', 'hi', 'hi', 'hi']
global global_resource
for i in range(len(global_resource)):
global_resource[i] = para
time.sleep(sleep)
print("修改全局变量为:", global_resource)
# 释放锁
lock.release()
def main():
thread_hi = threading.Thread(target=change_resource, args=('hi', 2))
thread_hello = threading.Thread(target=change_resource, args=('hello', 1))
thread_hi.start()
thread_hello.start()
if __name__ == '__main__':
main()
在使用锁时,可以正确依序完成两个线程,输出为:
修改全局变量为: ['hi', 'hi', 'hi', 'hi', 'hi']
修改全局变量为: ['hello', 'hello', 'hello', 'hello', 'hello']
不使用锁,输出为:
修改全局变量为: ['hello', 'hi', 'hi', 'hello', 'hello']
修改全局变量为: ['hello', 'hi', 'hi', 'hi', 'hi']
在第4秒时thread_hello执行输出,第8秒thread_hi执行输出。结果可见该时间位置global_resources最新的值。
值得注意的一点是,当被锁定的锁在加锁线程中再次acquire()时,该进程也被阻塞,只能在其它未被锁阻塞的线程中release(),否则就成为死锁。
3、threading.Rlock
在锁的基础上加入的所属线程和递归等级的概念。在线程未释放锁之前再次获取锁只会使锁的递归等级+1而不会死锁。只有在先获取锁的进程中释放所有锁(递归等级为0),其它线程才能获取锁。require()方法与lock一样返回上锁成功与否的True/False。
4、threading.Condition
threading.Condition(lock=None)
Condition传入一个锁或递归锁,使得在一个线程中调用其wait()方法时,释放锁并阻塞该线程,直到在其它线程中调用了该condition的notify()或notify_all()方法。此时,被阻塞的线程重新获得锁。
acquire(): 请求锁,若使用递归锁,则请求底层锁;
release(): 释放底层锁
wait(timeout=None): 释放锁并阻塞线程,直到其它线程notify(),notify_all(),并且release()掉condition或者超时。此时,被阻塞的线程重新获得锁。
wait_for(predicate, timeout=None): 与wait相似。首先调用predicate,如果predicate返回的是Ture,则不会释放锁,直接往后执行;若不为True,则释放锁,知道predicate的返回值是True。值得注意的是,wait_for()也要等到其他线程中notify或notify_all通知且释放锁之后,才会再计算predicate的值,若为True,则原进程获得锁继续执行,若为False,继续阻塞直到timeout。即:while not predicate(): condition_lock.wait()。无锁情况调用wait和wait_for都会报RuntimeError。
"""
让一个线程等待,直到另一个线程通知
"""
import time
import threading
# 创建条件变量对象
condition_lock = threading.Condition()
PRE = 0
# predicate可调用函数
def pre():
print(PRE)
return PRE
def test_thread_hi():
# 在使用wait/wait_for之前必须先获得锁
condition_lock.acquire()
print('等待线程test_thread_hello的通知')
# 先执行一次pre,返回False后释放掉锁,等另一个线程释放掉锁后再次执行pre,返回True后再次获取锁
# wait_for的返回值不是True和False,而是predicate参数的返回值
condition_lock.wait_for(pre)
# condition_lock.wait()
print('继续执行')
# 不要忘记使用wait/wait_for之后要释放锁
condition_lock.release()
def test_thread_hello():
time.sleep(1)
condition_lock.acquire()
global PRE
PRE = 1
print('修改PRE值为1')
print('通知线程test_thread_hi可以准备获取锁了')
condition_lock.notify()
# 先notify/notify_all之后在释放锁
condition_lock.release()
print('你获取锁吧')
def main():
thread_hi = threading.Thread(target=test_thread_hi)
thread_hello = threading.Thread(target=test_thread_hello)
thread_hi.start()
thread_hello.start()
if __name__ == '__main__':
main()
输出:
等待线程test_thread_hello的通知
0
修改PRE值为1
通知线程test_thread_hi可以准备获取锁了
你获取锁吧
1
继续执行
### 若使用wait,则输出:
等待线程test_thread_hello的通知
修改PRE值为1
通知线程test_thread_hi可以准备获取锁了
你获取锁吧
继续执行
5、threading.Semaphore
threading.Semphore(value=1)
threading的信号量对象。每调用一次acquire(blocking=True, Timeout=None) value-1,acquire返回True,如果在调用acquire前value已经为0,则调用acquire时阻塞当前线程直到使用release方法唤醒,acquire-1并返回True。blocking为False则Value为0不会阻塞。timeout设置后,时间内未获得信号则直接返回False,否则返回True,和前面一样。调用一次release() value+1,唤醒等待value大于0的线程。
"""
通过信号量对象管理一次性运行的线程数量
"""
import time
import threading
# 创建信号量对象,初始化计数器值为3
semaphore3 = threading.Semaphore(3)
def thread_semaphore(index):
# 信号量计数器减1
semaphore3.acquire()
time.sleep(2)
print('thread_%s is running...' % index)
# 信号量计数器加1
semaphore3.release()
def main():
# 虽然会有9个线程运行,但是通过信号量控制同时只能有3个线程运行
# 第4个线程启动时,调用acquire发现计数器为0了,所以就会阻塞等待计数器大于0的时候
for index in range(9):
threading.Thread(target=thread_semaphore, args=(index, )).start()
if __name__ == '__main__':
main()
输出:程序会三个三个执行。
6、threading.Event
用于不同进程间的相互通知,在建立时默认为False。方法如下:
is_set(): 当内部标志为True时返回True
set(): 将内部标志置为True。此时唤醒所有等待中的线程,调用wait()方法的线程将不会被阻塞
clear(): 将内部标志置为False。所有调用wait()方法的线程将被阻塞,直到调用set()方法
wait(): 阻塞当前线程直到内部标志位True或超时。如果调用时内部标志本身就是True,则不会被阻塞
"""
事件对象使用实例
"""
import time
import threading
# 创建事件对象,内部标志默认为False
event = threading.Event()
def student_exam(student_id):
print('学生%s等监考老师发卷。。。' % student_id)
event.wait()
print('开始考试了!')
def invigilate_teacher():
time.sleep(5)
print('考试时间到,学生们可以开始考试了!')
# 设置内部标志为True,并唤醒所有等待的线程
event.set()
def main():
for student_id in range(3):
threading.Thread(target=student_exam, args=(student_id, )).start()
threading.Thread(target=invigilate_teacher).start()
if __name__ == '__main__':
main()
输出:
学生0等监考老师发卷。。。
学生1等监考老师发卷。。。
学生2等监考老师发卷。。。
考试时间到,学生们可以开始考试了!
开始考试了!
开始考试了!
开始考试了!
7、threading.Timer
定时器,表明一个操作需要在等待一定时间后执行。
threading.Timer(interval, function, args=None, kwargs=None)
interval:定时器秒数
function:执行的函数
args,kwargs:funciton的参数
方法:
cancel(): 停止计时器,并取消该函数的执行。cancel只在计时器计时结束前生效,如果function已经开始执行,则不会生效。
import threading
import time
def hello(name):
print ("hello %sn" % name)
# global timer
# timer = threading.Timer(2.0, hello, ["Hawk"])
# timer.start()
if __name__ == "__main__":
timer = threading.Timer(2.0, hello, ["Hawk"]) ##每隔两秒调用函数hello
timer.start()
time.sleep(3)
timer.cancel()
输出:
hello Hawk
### 当time.sleep(1)时,被cancel无输出
8、threading.Barrier
栅栏对象,用于一些进程需要彼此等待的情况。每个线程都会尝试调用wait()方法,然后阻塞,直到所有线程都调用了wait()方法一起释放。
threading.Barrier(parties, action=None, timeout=None)
parties: 需要创建栅栏对象的线程数
action: 一个可调用的对象,在所有线程被释放时,在释放前,其中随机一个线程会调用action对象
timeout: wait()的超时时间
方法:
wait(timeout=None): 如果使用了wait()方法中的timeout参数,则此参数优先于栅栏定义时的timeout。返回值为range(parties)中的一个整数,此值每个线程都不同。如果action调用一场、超时均会报BrokenBarrierError。
reset(): 将一个栅栏对象重置为默认的初始态。如果此时有线程在等待释放,则报BrokenBarrierError。
abort(): 使栅栏进入BrokenBarrierError异常。当想要放弃一个线程但又不希望发生死锁时调用这个方法。
parties: 通过栅栏的线程数量
n_waiting:在栅栏中等待的线程数量
broken: 若栅栏破损返回True
"""
栅栏对象使用示例
"""
import time
import threading
def test_action():
print('所有栅栏线程释放前调用此函数!')
# 创建线程数为3的栅栏对象,当“拦住”3个线程的wait后放行,然后又继续“拦”(如果有的话)
barrier = threading.Barrier(3, test_action)
def barrier_thread(sleep):
time.sleep(sleep)
print('barrier thread-%s wait...' % sleep)
# 阻塞线程,直到阻塞线程数达到栅栏指定数量
barrier.wait()
print('barrier thread-%s end!' % sleep)
def main():
# 这里开启了6个线程,则一次会拦截3个
for sleep in range(6):
threading.Thread(target=barrier_thread, args=(sleep, )).start()
if __name__ == '__main__':
main()
输出:
barrier thread-0 wait...
barrier thread-1 wait...
barrier thread-2 wait...
所有栅栏线程释放前调用此函数!
barrier thread-2 end!
barrier thread-0 end!
barrier thread-1 end!
barrier thread-3 wait...
barrier thread-4 wait...
barrier thread-5 wait...
所有栅栏线程释放前调用此函数!
barrier thread-5 end!
barrier thread-3 end!
barrier thread-4 end!
python中with语法对threading的处理:
with thread_lock:
func()
等同于:
thread_lock.acquire()
try:
func()
finally:
thread_lock.release()
with会在开始获得锁并在func()执行完后释放锁。若在进入with语句前thread_lock是已经被获取的锁,则会持续等待知道在其它线程中lock被释放。
本文很大程度上参考了CNblog作者山上下了雪的文章,并在一些地方增加了样例和注解,原文链接如下:
Python内置库:threading(多线程操作) - 山上下了雪 - 博客园www.cnblogs.com