1、概述
在Python3中主要有3个线程模块,即:_thread、threading、queue模块;
_thread模块:在 3.7 版进行了更改,这个模块曾经是可选的,但现在总是可用的,之前叫thread。
_thread模块:提供了操作多个线程(也被称为 轻量级进程 或 任务)的底层原语 —— 多个控制线程共享全局数据空间。为了处理同步问题,也提供了简单的锁机制(也称为 互斥锁 或 二进制信号)。
threading 模块:这个模块在较低级的模块 _thread 基础上建立较高级的线程接口。
queue模块:实现了多生产者、多消费者队列。这特别适用于消息必须安全地在多线程间交换的线程编程。模块中的 Queue 类实现了所有所需的锁定语义。
本文案例在python3.8环境下运行。
2、_thread模块应用
使用这个模块之前,先导入它:
import _thread
如何来创建一个线程?
通过文档我们找到一个函数:
_thread.start_new_thread(function, args[, kwargs])
函数解释:
开启一个新线程并返回其标识。
function :线程执行函数;
args :附带参数列表 (必须是元组)。
kwargs :可选的 ,参数指定一个关键字参数字典。
当函数返回时,线程会静默地退出。
这与java类似,线程内语句执行完,线程会自动结束并退出。
当函数因某个未处理异常而终结时,sys.unraisablehook() 会被调用以处理异常。
钩子参数的 object 属性为 function。 在默认情况下,会打印堆栈回溯然后该线程将退出(但其他线程会继续运行)。
当函数引发 SystemExit 异常时,它会被静默地忽略。
举个例子:
import _thread
import time
# 自定义的线程内调用的函数
def show(threadName, delaySec, times):
while times > 0:
time.sleep(delaySec)
times -= 1
print("%s: %s" % (threadName, time.ctime(time.time())))
_thread.start_new_thread(show, ("Thread-1", 1, 5))
_thread.start_new_thread(show, ("Thread-2", 1, 4))
time.sleep(10) # 让主线程休眠10秒,足够子线程完成
当我们在主线程中使用start_new_thread创建新的线程的时,主线程无法得子知线程何时结束,导致主线程已经执行完了,子线程却还未完成,因此会使用这句time.sleep(10) 。
输出结果:
Thread-1: Sat Mar 21 10:52:35 2020
Thread-1: Sat Mar 21 10:52:35 2020
Thread-2: Sat Mar 21 10:52:35 2020
Thread-1: Sat Mar 21 10:52:36 2020
Thread-1: Sat Mar 21 10:52:36 2020
Thread-2: Sat Mar 21 10:52:36 2020
Thread-2: Sat Mar 21 10:52:37 2020
Thread-2: Sat Mar 21 10:52:38 2020
可以看到Thread-1和Thread-2交替打印,每次运行的结果都会有很大概率的不同。
前面例子提到 time.sleep(10) 使用主线成延时来等待子线程执行完成。但是有一些缺陷,我们需要大概估计子线程执行的时间然后设置延时,万一我们执行的子线程耗时不可估计,那么这时候使用延时这种方式就不太准确,因此我们采用另外一种方式,引入锁的概念,就是保证让子线程执行前加锁,执行完成释放锁,主线程根据锁的状态判断子线程是否执行完成来决定是否退出程序。
举个例子:
import _thread
import time
# 自定义的线程内调用的函数
def show(threadName, delaySec, times, lock):
while times > 0:
time.sleep(delaySec)
times -= 1
print("%s: %s" % (threadName, time.ctime(time.time())))
# 释放锁
lock.release()
myLock = _thread.allocate_lock() # 获取locktype对象
# 获取锁
myLock.acquire(1, 1)
_thread.start_new_thread(show, ("Thread-1", 0.5, 4, myLock))
# 可以试试放开下面这句话,同时运行两个线程,会导致输出结果并不全,解决这个问题我们可以使用threading模块
# _thread.start_new_thread(show, ("Thread-2", 0.5, 4, myLock))
# 获取锁的状态,当锁一直被子线程占用时,程序处于等待状态
while myLock.locked():
pass
输出结果:
Thread-1: Sat Mar 21 11:29:53 2020
Thread-1: Sat Mar 21 11:29:54 2020
Thread-1: Sat Mar 21 11:29:54 2020
Thread-1: Sat Mar 21 11:29:55 2020
Process finished with exit code 0
如果创建两个及以上的线程会导致输出结果不是预期的结果,要解决这个问题我们需要引入threading模块来解决这个问题
3、threading模块应用
使用这个模块之前,先导入它:
import threading
创建并启动线程:
举个例子:
import threading
import time
# 通过继承实现一个自己的线程类,类似java 的runnable
class MyThread(threading.Thread):
def __init__(self, threadId, threadName, delaySec, times):
threading.Thread.__init__(self)
self.threadId = threadId
self.threadName = threadName
self.delaySec = delaySec
self.times = times
# 重载threading.Thread 的run 方法,类似 java 实现 runnable 后实现run 方法
def run(self):
print("线程%s:run->开始" % (self.threadName))
show(self.threadId, self.threadName, self.delaySec, self.times)
print("线程%s:run->结束" % (self.threadName))
# 自定义的线程内调用的函数
def show(threadId, threadName, delaySec, times):
while times > 0:
time.sleep(delaySec)
times -= 1
print("%s: %s: %s" % (threadId, threadName, time.ctime(time.time())))
myThread = MyThread(1, "Thread-1", 0.5, 5)
myThread2 = MyThread(2, "Thread-2", 1, 5)
myThread.start()
myThread2.start()
# 将两个子线程加入主线程,等待子线程中止后再退出主线程。
# 应用场景:主线程生成并启动了子线程,而子线程里要进行大量的耗时的运算(这里可以借鉴下线程的作用),当主线程处理完其他的事务后,需要用到子线程的处理结果,这个时候就要用到join();方法了。
myThread.join()
myThread2.join()
print ("退出主线程")
例子中使用了join函数,什么时候使用它?
应用场景:主线程生成并启动了子线程,而子线程里要进行大量的耗时的运算(这里可以借鉴下线程的作用),当主线程处理完其他的事务后,需要用到子线程的处理结果,这个时候就要用到join()方法。
主线程:即每一个应用程序启动后都会创建一个主线程,在这个主线程中再创建的线程都叫子线程。
输出结果:
线程Thread-1:run->开始
线程Thread-2:run->开始
1: Thread-1: Sat Mar 21 12:03:17 2020
2: Thread-2: Sat Mar 21 12:03:18 2020
1: Thread-1: Sat Mar 21 12:03:18 2020
1: Thread-1: Sat Mar 21 12:03:18 2020
2: Thread-2: Sat Mar 21 12:03:19 2020
1: Thread-1: Sat Mar 21 12:03:19 2020
1: Thread-1: Sat Mar 21 12:03:19 2020
线程Thread-1:run->结束
2: Thread-2: Sat Mar 21 12:03:20 2020
2: Thread-2: Sat Mar 21 12:03:21 2020
2: Thread-2: Sat Mar 21 12:03:22 2020
线程Thread-2:run->结束
退出主线程
Process finished with exit code 0
可以看出线程一和线程而随机交替执行。
接下来我们需要解决上一节我们提到的,_thread模块在两个及以上的情况下,准确判断子线程是否都执行完,如果执行完则退出程序:
举个例子:
import threading
import time
# 实现一个自己的线程类,类似java 的runnable
class MyThread(threading.Thread):
def __init__(self, threadId, threadName, delaySec, times):
threading.Thread.__init__(self)
self.threadId = threadId
self.threadName = threadName
self.delaySec = delaySec
self.times = times
# 重载threading.Thread 的run 方法,类似 java 实现 runnable 后实现run 方法
def run(self):
print("线程%s:run->开始" % (self.threadName))
# 获取锁
threadLock.acquire()
show(self.threadId, self.threadName, self.delaySec, self.times)
# 释放锁,开启下一个线程
threadLock.release()
print("线程%s:run->结束" % (self.threadName))
# 自定义的线程内调用的函数
def show(threadId, threadName, delaySec, times):
while times > 0:
time.sleep(delaySec)
times -= 1
print("%s: %s: %s" % (threadId, threadName, time.ctime(time.time())))
# 获取线程锁对象
threadLock = threading.Lock()
threads = []
myThread = MyThread(1, "Thread-1", 0.5, 5)
myThread2 = MyThread(2, "Thread-2", 1, 5)
threads.append(myThread)
threads.append(myThread2)
myThread.start()
myThread2.start()
# 等待所有线程完成
for t in threads:
t.join()
print("退出主线程")
输出结果:
线程Thread-1:run->开始
线程Thread-2:run->开始
1: Thread-1: Sat Mar 21 12:11:35 2020
1: Thread-1: Sat Mar 21 12:11:36 2020
1: Thread-1: Sat Mar 21 12:11:36 2020
1: Thread-1: Sat Mar 21 12:11:37 2020
1: Thread-1: Sat Mar 21 12:11:37 2020
线程Thread-1:run->结束
2: Thread-2: Sat Mar 21 12:11:38 2020
2: Thread-2: Sat Mar 21 12:11:39 2020
2: Thread-2: Sat Mar 21 12:11:40 2020
2: Thread-2: Sat Mar 21 12:11:41 2020
2: Thread-2: Sat Mar 21 12:11:42 2020
线程Thread-2:run->结束
退出主线程
Process finished with exit code 0
通过这个输出结果我们看到了线程同步的效果,且保证子线程执行完后结束主线程。
特别提示:如果我们不想实现同步效果,继续保持异步,则只需要注释threadLock这个锁变量相关的3行代码。
4、queue模块应用
线程优队列
使用这个模块之前,先导入它:
import queue
queue模块实现了三种类型的队列,它们的区别仅仅是条目取回的顺序。
- FIFO 队列:先添加的任务先取回;
- LIFO 队列:最近被添加的条目先取回(操作类似一个堆栈);
- 优先级队:条目将保持排序( 使用 heapq 模块 ) 并且最小值的条目第一个返回。
举一个优先级队列的例子:
import queue
import threading
import time
exitFlag = 0
# 实现一个自己的线程类,类似java 的runnable
class MyThread(threading.Thread):
def __init__(self, threadId, threadName, que):
threading.Thread.__init__(self)
self.threadId = threadId
self.threadName = threadName
self.que = que
# 重载threading.Thread 的run 方法,类似 java 实现 runnable 后实现run 方法
def run(self):
print("线程%s:run->开始" % (self.threadName))
show(self.threadId, self.threadName, self.que)
print("线程%s:run->结束" % (self.threadName))
# 自定义的线程内调用的函数
def show(threadId, threadName, que):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty():
data = que.get()
queueLock.release()
print("%s: %s: %s,传递的数据:%s" % (threadId, threadName, time.ctime(time.time()), data))
else:
queueLock.release()
threadNameList = ["Thread-1", "Thread-2", "Thread-3", "Thread-4"]
nameList = ["One1", "Two2", "Three3", "Four4",'Five5','Six6','Seven7']
# 获取线程锁对象
queueLock = threading.Lock()
# 创建10个线程的队列空间
workQueue = queue.Queue(10)
threads = []
threadId = 1;
for threadName in threadNameList:
myThread = MyThread(threadId, threadName, workQueue)
myThread.start()
threads.append(myThread)
threadId += 1
queueLock.acquire()
for name in nameList:
workQueue.put(name)
queueLock.release()
# 等待队列清空
while not workQueue.empty():
pass
# 通知线程是时候退出
exitFlag = 1
# 等待所有线程完成
for t in threads:
t.join()
print("退出主线程")
输出结果:
线程Thread-1:run->开始
线程Thread-2:run->开始
线程Thread-3:run->开始
线程Thread-4:run->开始
2: Thread-2: Sat Mar 21 14:11:11 2020,传递的数据:One1
3: Thread-3: Sat Mar 21 14:11:11 2020,传递的数据:Two2
3: Thread-3: Sat Mar 21 14:11:11 2020,传递的数据:Three3
3: Thread-3: Sat Mar 21 14:11:11 2020,传递的数据:Four4
3: Thread-3: Sat Mar 21 14:11:11 2020,传递的数据:Five5
3: Thread-3: Sat Mar 21 14:11:11 2020,传递的数据:Six6
3: Thread-3: Sat Mar 21 14:11:11 2020,传递的数据:Seven7
线程Thread-3:run->结束
线程Thread-1:run->结束
线程Thread-4:run->结束线程Thread-2:run->结束
退出主线程
Process finished with exit code 0
例子创建了四个子线程输出,使用的队列容量为10,将nameList 数据放入队列,按照放入的顺序输出,并且nameList[0]第一个值先返回。
5、总结:
_thread不太推荐使用了,尤其是在多线程的时候,会出现预期的结果不正确。
推荐使用threading模块,处理多线程问题会得到我们想要的预期结果。
join() 这个函数是保证子线程执行完之后再退出主线程,防止内存泄漏或主线程执行完了子线程还未执行完的情况。
在调用.acquire()和.release()两个函数时IDE没有代码自动提示,比较奇怪。
参考链接:queue模块
参考链接:_thread模块
参考链接:threading模块