(十五)Python中级知识-线程模块

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模块

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值