1.threading函数
Python程序启动时,Python解释器会启动一个继承自threading.Thread的threading._MainThread线程对象作为主线程,所以涉及到threading.Thread的方法和函数时通常都算上了这个主线程的
- threading.active_count():返回当前存活的threading.Thread线程对象数量,等同于len(threading.enumerate())。
- threading.current_thread():返回此函数的调用者控制的threading.Thread线程对象。如果当前调用者控制的线程不是通过threading.Thread创建的,则返回一个功能受限的虚拟线程对象。
- threading.get_ident():返回当前线程的线程标识符。注意当一个线程退出时,它的线程标识符可能会被之后新创建的线程复用。
- threading.enumerate():返回当前存活的threading.Thread线程对象列表。
- threading.main_thread():返回主线程对象,通常情况下,就是程序启动时Python解释器创建的threading._MainThread线程对象。
2.线程对象:threading.Thread
threading.Thread目前还没有优先级和线程组的功能,而且创建的线程也不能被销毁、停止、暂定、恢复或中断。
守护线程:只有所有守护线程都结束,整个Python程序才会退出,但并不是说Python程序会等待守护线程运行完毕,相反,当程序退出时,如果还有守护线程在运行,程序会去强制终结所有守护线程,当守所有护线程都终结后,程序才会真正退出。可以通过修改daemon属性或者初始化线程时指定daemon参数来指定某个线程为守护线程。
非守护线程:一般创建的线程默认就是非守护线程,包括主线程也是,即在Python程序退出时,如果还有非守护线程在运行,程序会等待直到所有非守护线程都结束后才会退出。
threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
如果这个类的初始化方法被重写,请确保在重写的初始化方法中做任何事之前先调用threading.Thread类的__init__方法。
- group:应该设为None,即不用设置,使用默认值就好,因为这个参数是为了以后实现ThreadGroup类而保留的。
- target:在run方法中调用的可调用对象,即需要开启线程的可调用对象,比如函数或方法。
- name:线程名称,默认为“Thread-N”形式的名称,N为较小的十进制数。
- args:在参数target中传入的可调用对象的参数元组,默认为空元组()。
- kwargs:在参数target中传入的可调用对象的关键字参数字典,默认为空字典{}。
- daemon:默认为None,即继承当前调用者线程(即开启线程的线程,一般就是主线程)的守护模式属性,如果不为None,则无论该线程是否为守护模式,都会被设置为“守护模式”。
线程函数
- start():开启线程活动。它将使得run()方法在一个独立的控制线程中被调用,需要注意的是同一个线程对象的start()方法只能被调用一次,如果调用多次,则会报RuntimeError错误。
- run():此方法代表线程活动。
- join(timeout=None):让当前调用者线程(即开启线程的线程,一般就是主线程)等待,直到线程结束(无论它是什么原因结束的),timeout参数是以秒为单位的浮点数,用于设置操作超时的时间,返回值为None。如果想要判断线程是否超时,只能通过线程的is_alive方法来进行判断。join方法可以被调用多次。如果对当前线程使用join方法(即线程在内部调用自己的join方法),或者在线程没有开始前使用join方法,都会报RuntimeError错误。
- is_alive():线程是否存活,返回True或者False。在线程的run()运行之后直到run()结束,该方法返回True。
- daemon:表示该线程是否是守护线程,True或者False。设置一个线程的daemon必须在线程的start()方法之前,否则会报RuntimeError错误。这个值默认继承自创建它的线程,主线程默认是非守护线程的,所以在主线程中创建的线程默认都是非守护线程的,即daemon=False。
使用threading.Thread类创建线程简单示例:
#通过实例化threading.Thread类创建线程
import time
import threading
#定义线程
def test_thread(param='hi', sleep=3):
time.sleep(sleep)
print(param)
def main():
# 创建线程
thread_hi = threading.Thread(target=test_thread)
# 启动线程
thread_hi.start()
print('Main thread has ended!')
if __name__ == '__main__':
main()
#输出:Main thread has ended! hi
使用threading.Thread类的子类创建线程简单示例:
#通过继承threading.Thread的子类创建线程
import time
import threading
#TestThread继承threading.Thread
class TestThread(threading.Thread):
def __init__(self, param='hello', sleep=3):
# 重写threading.Thread的__init__方法时,确保在所有操作之前先调用threading.Thread.__init__方法
super().__init__()
self.para = param
self.sleep = sleep
def run(self):
#线程内容
time.sleep(self.sleep)
print(self.para)
def main():
# 创建线程
thread_hi = TestThread()
# 启动线程
thread_hi.start()
print('Main thread has ended!')
if __name__ == '__main__':
main()
#输出:Main thread has ended! hello
join方法简单示例:
#使用join方法阻塞主线程
import time
import threading
#定义线程函数
def test_thread(para='hello', sleep=5):
#线程运行函数
time.sleep(sleep)
print(para)
def main():
# 创建线程
thread_hello = threading.Thread(target=test_thread)
# 启动线程
thread_hello.start()
time.sleep(2)
print('马上执行join方法了')
# 执行join方法会阻塞调用线程(主线程),直到调用join方法的线程(thread_hello)结束
thread_hello.join()
print('线程thread_hello已结束')
# 这里不会阻塞主线程,因为运行到这里的时候,线程thread_hello已经运行结束了
print('Main thread has ended!')
if __name__ == '__main__':
main()
3.锁对象:threading.Lock
threading.Lock是直接通过_thread模块扩展实现的。
当锁在被锁定时,它并不属于某一个特定的线程。
锁只有“锁定”和“非锁定”两种状态,当锁被创建时,是处于“非锁定”状态的。当锁已经被锁定时,再次调用acquire()方法会被阻塞执行,直到锁被调用release()方法释放掉锁并将其状态改为“非锁定”。
同一个线程获取锁后,如果在释放锁之前再次获取锁会导致当前线程阻塞,除非有另外的线程来释放锁,如果只有一个线程,并且发生了这种情况,会导致这个线程一直阻塞下去,即形成了死锁。所以在获取锁时需要保证锁已经被释放掉了,或者使用递归锁来解决这种情况。
加锁
acquire(blocking=True, timeout=-1):获取锁,并将锁的状态改为“锁定”,成功返回True,失败返回False。当一个线程获得锁时,会阻塞其他尝试获取锁的线程,直到这个锁被释放掉。timeout默认值为-1,即将无限阻塞等待直到获得锁,如果设为其他的值时(单位为秒的浮点数),将最多阻塞等待timeout指定的秒数。当blocking为False时,timeout参数被忽略
释放锁
release():释放一个锁,并将其状态改为“非锁定”,需要注意的是任何线程都可以释放锁,不只是获得锁的线程(因为锁不属于特定的线程)。release()方法只能在锁处于“锁定”状态时调用,如果在“非锁定”状态时调用则会报RuntimeError错误。
使用锁实现线程同步的简单示例:
#使用锁实现线程同步
import time
import threading
# 创建锁
lock = threading.Lock()
# 全局变量
global_resource = [None] * 5
# 这段代码如果不加锁,第一个线程运行结束后global_resource中是乱的,输出为:修改全局变量为: ['小红', '小明', '小明', '小红', '小红']
# 第二个线程运行结束后,global_resource中还是乱的,输出为:修改全局变量为: ['小红', '小明', '小明', '小明', '小明']
def change_resource(param, sleep):
# 请求锁
lock.acquire()
global global_resource
for i in range(len(global_resource)):
global_resource[i] = param
time.sleep(sleep)
print("修改全局变量为:", global_resource)
# 释放锁
lock.release()
def main():
thread_hi = threading.Thread(target=change_resource, args=('小明', 2))
thread_hello = threading.Thread(target=change_resource, args=('小红', 1))
thread_hi.start()
thread_hello.start()
if __name__ == '__main__':
main()
4.递归锁对象:threading.RLock
递归锁和普通锁的差别在于加入了“所属线程”和“递归等级”的概念,释放锁必须有获取锁的线程来进行释放,同时,同一个线程在释放锁之前再次获取锁将不会阻塞当前线程,只是在锁的递归等级上加了1(获得锁时的初始递归等级为1)。
使用普通锁时,对于一些可能造成死锁的情况,可以考虑使用递归锁来解决。
加锁
acquire(blocking=True, timeout=-1):与普通锁的不同之处在于:当使用默认值时,如果这个线程已经拥有锁,那么锁的递归等级加1。线程获得锁时,该锁的递归等级被初始化为1。当多个线程被阻塞时,只有一个线程能在锁被解时获得锁,这种情况下,acquire()是没有返回值的。
释放锁
release():没有返回值,调用一次则递归等级减1,递归等级为零时表示这个线程的锁已经被释放掉,其他线程可以获取锁了。可能在一个线程中调用了多次acquire(),导致锁的递归等级大于了1,那么就需要调用对应次数的release()来完全释放锁,并将它的递归等级减到零,其他的线程才能获取锁,不然就会一直被阻塞着。
递归锁使用简单示例:
#在普通锁中可能造成死锁的情况,可以考虑使用递归锁解决
import time
import threading
# 使用成一个递归锁就可以解决当前这种死锁情况
rlock_1 = rlock_2 = threading.RLock()
def thread_1():
# 初始时锁内部的递归等级为1
rlock_1.acquire()
print('线程thread_1获得了锁rlock_1')
time.sleep(2)
# 如果再次获取同样一把锁,则不会阻塞,只是内部的递归等级加1
rlock_2.acquire()
print('线程thread_1获得了锁rlock_2')
# 释放一次锁,内部递归等级减1
rlock_2.release()
# 这里再次减,当递归等级为0时,其他线程才可获取到此锁
rlock_1.release()
def thread_2():
rlock_2.acquire()
print('线程thread_2获得了锁rlock_2')
time.sleep(2)
rlock_1.acquire()
print('线程thread_2获得了锁rlock_1')
rlock_1.release()
rlock_2.release()
def main():
thread_t1 = threading.Thread(target=thread_1)
thread_t2 = threading.Thread(target=thread_2)
thread_t1.start()
thread_t2.start()
if __name__ == '__main__':
main()
5.信号量对象:threading.Semaphore
一个信号量管理一个内部计数器,acquire()方法会减少计数器,release()方法则增加计数器,计数器的值永远不会小于零,当调用acquire()时,如果发现该计数器为零,则阻塞线程,直到调用release()方法使计数器增加。
threading.Semaphore(value=1):value参数默认值为1,如果指定的值小于0,则会报ValueError错误。一个信号量对象管理一个原子性的计数器,代表release()方法调用的次数减去acquire()方法的调用次数,再加上一个初始值。
- acquire(blocking=True, timeout=None):默认情况下,在进入时,如果计数器大于0,则减1并返回True,如果等于0,则阻塞直到使用release()方法唤醒,然后减1并返回True。被唤醒的线程顺序是不确定的。如果blocking设置为False,调用这个方法将不会发生阻塞。timeout用于设置超时的时间,在timeout秒的时间内没有获取到信号量,则返回False,否则返回True。
- release():释放一个信号量,将内部计数器增加1。当计数器的值为0,且有其他线程正在等待它大于0时,唤醒这个线程。
信号量对象简单示例:
#通过信号量对象管理一次性运行的线程数量
import time
import threading
# 创建信号量对象,初始化计数器值为4
semaphore4 = threading.Semaphore(4)
def thread_semaphore(index):
# 信号量计数器减1
semaphore4.acquire()
time.sleep(2)
print('thread_%s is running...' % index)
# 信号量计数器加1
semaphore4.release()
def main():
# 虽然会有9个线程运行,但是通过信号量控制同时只能有4个线程运行
# 第5个线程启动时,调用acquire发现计数器为0了,所以就会阻塞等待计数器大于0的时候
for index in range(9):
threading.Thread(target=thread_semaphore, args=(index, )).start()
if __name__ == '__main__':
main()
6. 条件变量对象:threading.Condition
threading.Condition(lock=None):一个条件变量对象允许一个或多个线程等待,直到被另一个线程通知。lock参数必须是一个Lock对象或者RLock对象,并且会作为底层锁使用,默认使用RLock。
它的wait()方法释放锁,并阻塞程序直到其他线程调用notify()或者notify_all()方法唤醒,然后wait()方法重新获取锁,这个方法也可以指定timeout超时时间。
它的notify()方法唤醒一个正在等待的线程,notify_all()则是唤醒所有正在等待的线程。notify()或者notify_all()并不会释放锁,所以被唤醒的线程并不会立即从它们的wait()方法出返回并执行,只有在调用了notify()或者notify_all()方法的线程放弃了锁的所有权后才会返回对应的线程并执行,即先通知再释放锁。
- wait(timeout=None):释放锁,等待直到被通知(再获取锁)或者发生超时事件。如果线程在调用此方法时本身并没有锁(即线程首先得有锁),则会报RuntimeError错误。这个方法释放底层锁,然后阻塞线程,直到另一个线程中的同一个条件变量使用notify()或notify_all()唤醒,或者超时事件发生,一旦被唤醒或者超时,则会重新去获取锁并返回(成功返回True,否则返回False)。timeout参数为浮点类型的秒数。在RLock中使用一次release方法,可能并不能释放锁,因为锁可能被acquire()了多次,但是在条件变量对象中,它调用了RLock类的内部方法,可以一次就完全释放锁,重新获取锁时也会重置锁的递归等级。
- notify(n=1):唤醒一个等待这个条件的线程,如果调用这个方法的线程在没有获得锁的情况下调用这个方法,会报RuntimeError错误。默认唤醒一个线程,可以通过参数n设置唤醒n个正在等待这个条件变量的线程,如果没有线程在等待,调用这个方法不会发生任何事。如果等待的线程中正好有n个线程,那么这个方法可以准确的唤醒这n个线程,但是等待的线程超过指定的n个,有时候可能会唤醒超过n个的线程,所以依赖参数n是不安全的行为。
- notify_all():唤醒所有等待这个条件的线程。这个方法与notify()不同之处在于它唤醒所有线程,而不是特定n个。
条件变量简单示例:
#让一个线程等待,直到另一个线程通知
import time
import threading
# 创建条件变量对象
condition_lock = threading.Condition()
PRE = 0
# predicate可调用函数
def pre():
print(PRE)
return PRE
def test_thread_1():
# 在使用wait/wait_for之前必须先获得锁
condition_lock.acquire()
print('等待线程test_thread_2的通知')
# 先执行一次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_2():
time.sleep(1)
condition_lock.acquire()
global PRE
PRE = 1
print('修改PRE值为1')
print('通知线程test_thread_1可以准备获取锁了')
condition_lock.notify()
# 先notify/notify_all之后在释放锁
condition_lock.release()
print('你获取锁')
def main():
thread_1 = threading.Thread(target=test_thread_1)
thread_2 = threading.Thread(target=test_thread_2)
thread_1.start()
thread_2.start()
if __name__ == '__main__':
main()
7.事件对象:threading.Event
一个事件对象管理一个内部标志,初始状态默认为False,set()方法可将它设置为True,clear()方法可将它设置为False,wait()方法将线程阻塞直到内部标志的值为True。
如果一个或多个线程需要知道另一个线程的某个状态才能进行下一步的操作,就可以使用线程的event事件对象来处理。
方法:
- is_set():当内部标志为True时返回True。
- set():设置内部标志为True。此时所有等待中的线程将被唤醒,调用wait()方法的线程将不会被阻塞。
- clear():将内部标志设置为False。所有调用wait()方法的线程将被阻塞,直到调用set()方法将内部标志设置为True。
- wait(timeout=None):阻塞线程直到内部标志为True,或者发生超时事件。如果调用时内部标志就是True,那么不会被阻塞,否则将被阻塞。timeout为浮点类型的秒数。
事件对象简单示例:
#事件对象使用实例
import time
import threading
# 创建事件对象,内部标志默认为False
event = threading.Event()
def student_exam(student_id):
print('学生%s等监考老师发卷。。。' % student_id)
event.wait()
print('学生%s开始考试了!' % student_id)
def invigilate_teacher():
time.sleep(5)
print('考试时间到,学生们可以开始考试了!')
# 设置内部标志为True,并唤醒所有等待的线程
event.set()
def main():
for student_id in range(4):
threading.Thread(target=student_exam, args=(student_id, )).start()
threading.Thread(target=invigilate_teacher).start()
if __name__ == '__main__':
main()
8.生产者和消费者示例
from threading import Thread,current_thread
import time
import random
from queue import Queue
#定义队列
queue = Queue(5)
#定义生产者
class ProducerThread(Thread):
def run(self):
name = current_thread().getName()
nums = range(100)
global queue
while True:
num = random.choice(nums)
queue.put(num)
print('生产者 %s 生产了数据 %s' %(name, num))
t = random.randint(1,3)
time.sleep(t)
print('生产者 %s 睡眠了 %s 秒' %(name, t))
#定义消费者
class ConsumerTheard(Thread):
def run(self):
name = current_thread().getName()
global queue
while True:
num = queue.get()
queue.task_done()
print('消费者 %s 消耗了数据 %s' %(name, num))
t = random.randint(1,5)
time.sleep(t)
print('消费者 %s 睡眠了 %s 秒' % (name, t))
p1 = ProducerThread(name = 'p1')
p1.start()
p2 = ProducerThread(name = 'p2')
p2.start()
p3 = ProducerThread(name = 'p3')
p3.start()
c1 = ConsumerTheard(name = 'c1')
c1.start()
c2 = ConsumerTheard(name = 'c2')
c2.start()