1. threading下的函数
1.1 threading.active_count()
返回当前处于alive状态的Thread对象的个数
1.2 threading.current_thread()
返回当前的Thread对象,对应于调用者控制的线程。如果调用者控制的线程不是通过threading模块创建的,则返回一个只有有限功能的虚假线程对象。
import threading
def func():
t = threading.current_thread()
print(id(t))
t2 = threading.Thread(target=func)
print(id(t2))
t2.start()
打印结果,可以看到t2和在线程中启动的func函数中的t对象是同一个。
47120752
47120752
1.3 thread.get_ident()
返回当前线程的“线程标识符”,是一个非0的整数,没有任何意义。
1.4 threading.enumerate()
返回当前活着的Thread对象的列表,包括守护线程,主线程和由current_thread()创建的虚假线程对象,但不包括终止线程和未开始的线程(即没有调用start函数的线程)
返回的是线程对象列表,跟常用的enumerate()函数还是不一样的,enumerate()函数返回的是 索引 + 值,这么写代码就可以看出区别了。
import threading
for index,value in enumerate(threading.enumerate()):
print(index,value.name)
打印结果:
0 MainThread
1.5 threading.main_thread()
返回主线程对象
1.6 threading.stack_size([size])
返回创建新的线程时,该线程使用的栈的大小。size参数可选,是一个整数,不是列表。size值为0或者至少为72768的正整数值。
1.7 threading.TIMEOUT_MAX
这个参数表示阻塞函数(Lock.acquire(),RLock.acquire(),Condition.wait())所允许等待的最长时限。超过该时间将引发OverflowError。
2. Thread-Local Data
由threading.local()创建local对象,每个线程都有单独的local对象,用来保存自己的数据,不与其他线程共享。
import threading
local = threading.local()
def func():
print('current_thread.name =', threading.current_thread().name)
local.value = threading.current_thread().name
def func2():
print('current_thread.name =', threading.current_thread().name)
print(local.value)
t1 = threading.Thread(target=func)
t1.start()
t1.join()
func2()
打印结果,在Thread-1中将value存入local中,但在主线程中无法从local中读取value值,说明local中保存的值都只在当前线程有效。
current_thread.name = Thread-1
current_thread.name = MainThread
Traceback (most recent call last):
File "E:\FileAndDocuments\Code\Python\0822\test.py", line 17, in
func2()
File "E:\FileAndDocuments\Code\Python\0822\test.py", line 11, in func2
print(local.value)
AttributeError: '_thread._local' object has no attribute 'value'
3. Thread对象
两种使用方式,1.继承Thread类,并重写init函数和run函数;2.创建threading.Thread对象。
Thread类表示在单独的线程中控制运行的活动。一旦创建了一个对象,必须通过调用线程的start()函数来启动,而直接调用run()函数的话,只是相当于在主线程中调用了一个普通的函数,起不到多线程的效果。
只要线程启动,就被视为"活着",当run()函数终止活着抛出了一个未处理的异常时,那么该线程便相当于不再"活着"了。is_alive()函数可以得到是否活着的结果。
线程可以调用其他线程的join()函数,这个造成线程阻塞,直到本线程出现异常终止或者被调用的线程结束。
3.1 Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
group: 不需要传入值,保留为做ThreadGroup类的未来扩展
target: 被线程调用的函数,默认为None,在传入值时,不能添加括号(经常不小心就加上了括号了)
name: 线程的名字
args: 给被调用函数的传参(target),是一个元组,所以如果传入的是一个参数的话,那么需要写成args=(xxx,),不要漏掉 ","
kwargs: 传参,关键字参数
daemon: 是否设置成守护线程。
3.2 start()
启动线程。
每个线程只能调用一次,如果调用多次将会抛出RuntimeError异常。
启动线程只能调用start()函数,不能直接调用run()函数
3.3 run()
线程实际执行的函数。如果是自定义Thread类,那么需要重写该函数。
3.4 join(timeout=None)
该函数被调用时,将会阻塞线程,知道被调用者的线程1.正常结束2.抛出异常3.等待超时
timeout如果需要设置,那么应该传入一个float类型的值。join()函数会返回None,所以需要需要使用is_alive()函数的话,应该在join()之前。如果timeout不存在或者为None,那么线程将阻塞直至线程结束。
一个join()函数可以调用多次,如果一个线程调用自身的join()函数,将会抛出RuntimeError异常,因为会导致死锁。
3.5 name,setName(),getName()
线程名称
3.6 ident
线程的ID号,一个非0的整数
3.7 is_alive()
返回线程是否还活着,在调用start()函数之后返回True
3.8 daemon,isDaemon(),setDaemon()
判断是否是守护线程,设置守护线程必须在start()函数之前执行。守护线程的意思是,假设将线程A设置成主线程的守护线程,不管线程A是否结束,都将随着主线程的结束而结束。
import threading
import time
def fun():
for _ in range(1000):
print(_)
time.sleep(0.01)
t = threading.Thread(target=fun)
t.setDaemon(True) # 设置成守护线程
t.start()
time.sleep(0.1)
print('end')
打印结果:
将线程t设置成主线程的守护线程后,主线程结束时,守护线程也随着结束了。
0
1
2
3
4
5
6
7
8
9
10
end
4.RLock对象
一个可重入(reentrant )的原子锁(同一个线程多次获取锁,释放锁,跟Lock不同,Lock只能获取一次)。
通过acquire()函数获取锁;使用release()函数释放锁。acquire()函数和release()函数可以相互嵌套。只有当最后一个release()函数被调用后,线程才是真正释放锁。
4.1 thread.RLock
一个可重入锁必须由获取它的线程释放,一个线程在获取了一个锁之后,可以再次获取它而不会造成阻塞现象。如果想要释放锁,那么调用了多少次acquire()函数,便要调用相同次数的release()函数。
4.2 acquire(blocking=True, timeout=-1)
获取锁,可以设置参数blocking的值,如果是True,则是可阻塞的,为False,则是不阻塞的。
在一个线程中,可以多次调用该函数;如果在另一个线程中调用该函数,如果在某个线程中已经拥有锁了,那么便会进入等待状态;acquire()函数会返回一个boolean值,True表示拥有了锁,False表示没有拿到锁。
如果设置了blocking=False,那么便不可设置timeout的值。
4.3 release()
释放锁。减去一层递归层数,如果锁的递归层数为0了,那么将重置线程为未锁定状态。该函数只能由本线程调用,如果在其他线程中被调用,将抛出RuntimeError异常。在调用本函数之前,需要保证线程已经获取到了,否则将抛出异常。
4.4 实例
import threading
import time
rlock = threading.RLock()
def func1():
print('-----func1.start')
# 获取锁
rlock.acquire()
print('-----func1.acquire')
time.sleep(5)
print('-----func1.test')
rlock.release()
print('-----func1.release')
print('-----func1.end')
def func2():
print('func2.start')
# 设置为阻塞模式,并且设置超时时间为1秒,如果1秒后没有获取锁,则往下执行
print(rlock.acquire(blocking=True,timeout=1.0))
print('func2.acquire')
time.sleep(3)
print('func2.test')
rlock.release()
print('func2.release')
print('func2.end')
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
打印结果,分析:
t1和t2线程启动时,先调用了func1()函数,t1线程获取了锁;调用func2()函数时,t2线程无法获取到锁,因为此时锁在t1上,而又因为在t2线程中的acquire()函数设置了blocking=True,timeout=1.0,即阻塞+超时1秒,t1线程的锁需要在5秒之后才会释放,那么在t2中1秒后是无法获取到锁的,所以acquire()函数返回了False;同样的,t2线程没有获取锁,那么在调用release()函数时,就会抛出RuntimeError异常。但如果你在另一个线程中没有调用acquire()和release()函数,线程调用的函数会正常执行,与其他线程并行处理。
-----func1.start
-----func1.acquire
func2.start
False
func2.acquire
func2.test
Exception in thread Thread-2:
Traceback (most recent call last):
File "F:\Python\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "F:\Python\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "E:\FileAndDocuments\Code\Python\0822\test.py", line 22, in func2
rlock.release()
RuntimeError: cannot release un-acquired lock
-----func1.test
-----func1.release
-----func1.end
5. Condition 对象
python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release函数外,还提供了wait和notify函数。线程首先acquire一个条件变量,然后判断一些条件,如果条件不满足则wait,如果条件满足,进行一些处理改变条件后,通过notify或者notify_all函数通知其他线程,其他处于wait状态的线程接收到通知后会重新判断提交。不断的重复上面的过程,从而解决复杂的同步问题。
使用with语句在封闭块的持续时间内获取关联的锁,acquire()函数和release()函数也调用相关锁的响应函数。
condition = threading.Condition()
with condition:
pass
其他函数被调用时线程需要持有锁。wait()函数会释放锁,然后进入阻塞状态,直到另一个线程通过调用notify()或者notify_all()唤醒它。当被唤醒后,wait()重新获取锁并返回。
notify()和notify_all()函数不会释放锁,意思是,如果调用了这两个函数时,需要再调用relsease()函数时才能完全唤醒其他线程。
在使用Condition时,最好使用while循环来判断是否需要进入wait状态
condition = threading.Condition()
with condition:
while xxxxx:
condition.wait()
5.1 threading.Condition(lock=None)
条件变量对象。一个条件变量允许一个或多个线程等待直至他们收到另一个线程的唤醒通知。如果想给Condition传入参数,那么应该是Lock对象或者RLock对象。
5.2 acquire(*args)
获取锁。返回boolean值,表示是否获取到了锁。
5.3 release()
释放锁。
5.4 wait(timeout=None)
进入等待状态,直至被唤醒(notify() or notify_all())或者超时时间到点(timeout)。当wait()函数被调用时如果该线程没有获取锁的话,将会抛出RuntimeError异常。
timeout参数的值如果要设置的话,需要传入一个浮点类型的数。如果Condition接收的参数是RLock对象的话,在调用release()函数后,可能不会释放锁,因为有可能会多次获取锁。
5.5 wait_for(predicate, timeout=None)
等待直到条件为True。predicate应该是一个callable,返回值为布尔值。timeout用于指定超时时间。该方法相当于反复调用wait()直到条件为真,或者超时。返回值是最后的predicate的返回值,或者超时返回Flase。
忽略超时特性,调用这个方法相当于:
while not predicate():
cv.wait()
实例:
import threading
import time
condition = threading.Condition(threading.RLock())
value = False
def func():
time.sleep(2)
print('func.value =',value)
return value
def func2():
with condition:
# 持锁
if condition.acquire():
# 等待func的值返回
condition.wait_for(func)
print('AAAAAAAAAAAA')
condition.release()
def func3():
with condition:
if condition.acquire():
time.sleep(10)
global value
# 更改了值
value = True
print('func3.value =',value)
# 唤醒,如果要调用该函数,本线程也需要持锁
condition.notify()
# 释放
condition.release()
t = threading.Thread(target=func2)
t2 = threading.Thread(target=func3)
t.start()
t2.start()
打印结果:
func.value = False
func3.value = True
func.value = True
count = 1
AAAAAAAAAAAA
PS:最开始理解错误的时候,写了下面的笔记,现在看看就行了。
在wait_for()中,需要传入一个函数对象,不能是boolean类型,不能是字符串,不能是函数调用(即下面代码中的func(),只能是func)。
import threading
import time
condition = threading.Condition(threading.RLock())
value = False
def func():
time.sleep(2)
print('func.value =',value)
return value
def func2():
with condition:
# 当执行到该行代码时,会调用func函数
# timeout=2表示2秒后超时
# 当超时后,会再次调用func函数,判断其返回值,在这次时,不管值是否为真还是为假,都将执行之后的代码
count = 1
while not condition.wait_for(func,timeout=4):
print('in wait_for')
count += 1
print('count =',count)
print('AAAAAAAAAAAA')
def func3():
time.sleep(20)
global value
value = True
print('func3.value =',value)
t = threading.Thread(target=func2)
t2 = threading.Thread(target=func3)
t.start()
t2.start()
打印结果:
condition.wait_for()有一只返回值,它的返回值与func的返回值一致。
从打印的结果来看,condition.wait_for会调用func函数两次,一次是正常执行,一次是timeout到点的时候。在两次判断中,如果第一次func返回的值False,那么将会继续等待;如果第二次调用func时,返回值不管是True,还是False,都将往下执行代码。所以如果需要让线程一直等待的话,那么就需要用上while循环了。
至于为什么要这么写?如果不传入timeout参数的话,condition.wait_for(func),如果在第一次func返回了False之后,那么该线程将永远处于阻塞状态,如果持有了锁,将永远不会释放锁,所以需要配合while循环来使用。
func.value = False
func.value = False
in wait_for
func.value = False
func.value = False
in wait_for
func.value = False
func3.value = True
func.value = True
count = 3
AAAAAAAAAAAA
笔记结束
5.5 notify(n=1)
默认情况下,唤醒一个等待线程。如果调用该方法的线程没有获取锁,则RuntimeError被抛出。
这个方法子多唤醒n(默认为1)个等待线程;如果没有线程等待,则没有操作。
如果至少n个线程正在等待,当前的实现是刚好唤醒n个线程。然而,依赖这个行为是不安全的,因为,未来某些优化后的实现可能会唤醒超过n个线程。
注意:一个唤醒的线程只有当请求到锁后才会从wait()调用中返回。由于notify()不释放锁,所以它的调用者应该释放锁。
5.6 notify_all()
唤醒所有等待线程。这个方法的行为类似于notify(),但是唤醒所有等待线程。如果调用线程未获取锁,则RuntimeError被抛出。
5.7 实例
轮流打印abc
import threading
import time
value = 'a'
condition = threading.Condition(threading.RLock())
def print_a():
global value
with condition:
while True:
if condition.acquire():
# 等待直到value的值为a
condition.wait_for(lambda :value=='a')
print(value)
# 更改value的值
value = 'b'
# 唤醒其他所有线程
condition.notifyAll()
# 释放
condition.release()
time.sleep(2)
def print_b():
global value
with condition:
while True:
if condition.acquire():
condition.wait_for(lambda :value=='b')
print(value)
value = 'c'
condition.notifyAll()
condition.release()
time.sleep(2)
def print_c():
global value
with condition:
while True:
if condition.acquire():
condition.wait_for(lambda :value=='c')
print(value)
value = 'a'
condition.notifyAll()
condition.release()
time.sleep(2)
t1 = threading.Thread(target=print_a)
t2 = threading.Thread(target=print_b)
t3 = threading.Thread(target=print_c)
t1.start()
t2.start()
t3.start()
6.参考
https://yiyibooks.cn/xx/python_352/library/threading.html
https://docs.python.org/3/library/threading.html?highlight=threading#module-threading
https://blog.csdn.net/tomato__/article/details/46606045