源码下载
Python程序启动时,Python解释器会启动一个继承自threading.Thread的threading._MainThread线程对象作为主线程,所以涉及到threading.Thread的方法和函数时通常都算上了这个主线程的
threading.Thread目前还没有优先级和线程组的功能,而且创建的线程也不能被销毁、停止、暂定、恢复或中断。
守护线程:只有所有守护线程都结束,整个Python程序才会退出,但并不是说Python程序会等待守护线程运行完毕,相反,当程序退出时,如果还有守护线程在运行,程序会去强制终结所有守护线程,当守所有护线程都终结后,程序才会真正退出。可以通过修改daemon属性或者初始化线程时指定daemon参数来指定某个线程为守护线程。
非守护线程:一般创建的线程默认就是非守护线程,包括主线程也是,即在Python程序退出时,如果还有非守护线程在运行,程序会等待直到所有非守护线程都结束后才会退出。
threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
如果这个类的初始化方法被重写,请确保在重写的初始化方法中做任何事之前先调用threading.Thread类的__init__方法。
#通过实例化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的子类创建线程
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方法阻塞主线程
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()
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()
递归锁和普通锁的差别在于加入了“所属线程”和“递归等级”的概念,释放锁必须有获取锁的线程来进行释放,同时,同一个线程在释放锁之前再次获取锁将不会阻塞当前线程,只是在锁的递归等级上加了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()
一个信号量管理一个内部计数器,acquire()方法会减少计数器,release()方法则增加计数器,计数器的值永远不会小于零,当调用acquire()时,如果发现该计数器为零,则阻塞线程,直到调用release()方法使计数器增加。
threading.Semaphore(value=1):value参数默认值为1,如果指定的值小于0,则会报ValueError错误。一个信号量对象管理一个原子性的计数器,代表release()方法调用的次数减去acquire()方法的调用次数,再加上一个初始值。
#通过信号量对象管理一次性运行的线程数量
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()
threading.Condition(lock=None):一个条件变量对象允许一个或多个线程等待,直到被另一个线程通知。lock参数必须是一个Lock对象或者RLock对象,并且会作为底层锁使用,默认使用RLock。
它的wait()方法释放锁,并阻塞程序直到其他线程调用notify()或者notify_all()方法唤醒,然后wait()方法重新获取锁,这个方法也可以指定timeout超时时间。
它的notify()方法唤醒一个正在等待的线程,notify_all()则是唤醒所有正在等待的线程。notify()或者notify_all()并不会释放锁,所以被唤醒的线程并不会立即从它们的wait()方法出返回并执行,只有在调用了notify()或者notify_all()方法的线程放弃了锁的所有权后才会返回对应的线程并执行,即先通知再释放锁。
#让一个线程等待,直到另一个线程通知
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()
一个事件对象管理一个内部标志,初始状态默认为False,set()方法可将它设置为True,clear()方法可将它设置为False,wait()方法将线程阻塞直到内部标志的值为True。
如果一个或多个线程需要知道另一个线程的某个状态才能进行下一步的操作,就可以使用线程的event事件对象来处理。
方法:
#事件对象使用实例
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()
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()
源码下载
若此文档不够详细,可以参考十分钟学会python_哔哩哔哩_bilibili