在Python程序中,线程模块是一种广泛应用于软件开发的技术,引入线程可以提高程序并发执行的程度,并进一步提高系统效率。我们可以通过“_thread” 和threading (推荐使用)这两个模块来处理线程。thread模块在Python3.8版本中已经完全移除,转而使用threading模块。所以,在Python3.8版本中不能再使用thread模块,但是为了兼容Python3.8以前的程序,已经将thread模块更名为“_thread”模块。
_thread模块
_thread模块是Python早期的线程模块,它提供了一些基本的线程操作函数,用法十分简单,它的核心其实就是start_new_thread()函数。
_thread.start_new_thread(函数名, (参数))函数会直接开启一个线程,该函数的第一个参数需要指定一个函数(也就是我们想要加入多线程的方法),可以把这个函数称为线程函数,当线程启动时会自动调用这个函数。第二个参数是给线程函数传递参数,必须是元组类型。
为了对比单线程和多线程处理数据的速度,下面我们首先使用单线程执行程序:
动手练一练:
# 导入模块
import _thread
import time
def fun1():
print("开始运行fun1:", time.ctime())
time.sleep(2)
print("运行fun1结束:", time.ctime())
def fun2():
print("开始运行fun2:", time.ctime())
time.sleep(1)
print("运行fun2结束:", time.ctime())
def main():
print("开始运行")
fun1()
fun2()
print("运行结束")
if __name__=='__main__':
main()
执行以上代码,输出结果为:
开始运行
开始运行fun1: Tue Apr 23 10:07:44 2024
运行fun1结束: Tue Apr 23 10:07:46 2024
开始运行fun2: Tue Apr 23 10:07:46 2024
运行fun2结束: Tue Apr 23 10:07:47 2024
运行结束
上面的例子中,从程序运行结果可以看出,程序的运行时间前后花了3秒,因为单线程中的函数不是“同步”执行的,必须等到fun1函数执行完,才能切换执行fun2函数,如果想要让fun1函数和fun2函数同时执行,这就需要使用_thread模块创建多线程来缩短执行时间,例如:
动手练一练:
# 导入模块
import _thread
import time
def fun1():
print("开始运行fun1:", time.ctime())
time.sleep(2)
print("运行fun1结束:", time.ctime())
def fun2():
print("开始运行fun2:", time.ctime())
time.sleep(1)
print("运行fun2结束:", time.ctime())
def main():
print("开始运行")
# 创建线程
_thread.start_new_thread(fun1,())
_thread.start_new_thread(fun2,())
time.sleep(3)
print("运行结束")
if __name__=='__main__':
main()
执行以上代码,输出结果为:
开始运行
开始运行fun1: Tue Apr 23 10:23:37 2024
开始运行fun2: Tue Apr 23 10:23:37 2024
运行fun2结束: Tue Apr 23 10:23:38 2024
运行fun1结束: Tue Apr 23 10:23:39 2024
运行结束
上面的例子中,从程序运行结果可以看出,_thread模块的start_new_thread方法提供了简单的多线程机制,程序的运行时间前后只花了2秒。在fun1线程执行时,fun2线程也在“同步”地执行,因为两个线程开始执行的时间一模一样。并且两个线程会根据自己线程内的time.sleep()休眠时间执行,每个线程互相独立运行,互不影响。所以引入多线程可以明显提高程序的运行速度。注意,由于fun1线程休眠时间多了一秒,所以fun1线程结束时间会晚一秒。
我们还可以为线程函数传递参数,通过start_new_thread()函数的第二个参数可以为线程函数传递参数,该参数必须是元组,例如:
动手练一练:
import _thread
import time
def fun1(a, b):
print("开始运行fun1,线程的名:", a)
time.sleep(b)
print("运行fun1结束")
def fun2(a, b):
print("开始运行fun2,线程的名:", a)
time.sleep(b)
print("运行fun2结束")
def main():
print("开始运行")
# 创建线程
_thread.start_new_thread(fun1,("第一个线程",2))
_thread.start_new_thread(fun2,("第二个线程",2))
time.sleep(4)
print("运行结束")
if __name__=='__main__':
main()
执行以上代码,输出结果为:
开始运行
开始运行fun1,线程的名: 第一个线程
开始运行fun2,线程的名: 第二个线程
运行fun1结束
运行fun2结束
运行结束
在上面的代码中,我们为线程函数传递参数,让程序产生多个线程。这里需要注意的是,main()函数为主线程,用来控制整个进程从开始到结束的全部操作。在main()函数的最后必须使用time.sleep(4)函数让程序处于休眠状态,目的是为了在所有线程执行完之前,让程序暂停4秒钟,阻止程序退出。如果main()函数里面没有使用time.sleep(4)函数让程序暂停,主线程执行完就会立马退出,所有的线程也会直接关闭,因为main()函数无法判断是否有线程正在执行,以及是否所有线程函数都执行完毕,所以我们只能采用估算时间的方法让程序暂停4秒,直到所有的线程函数都执行完毕再退出。
在复杂的Python程序开发中,我们有时候很难估算线程的实际执行时间,主线程过早或者过晚的退出都不是我们所期盼的,这时候我们就需要使用线程锁。线程锁是通过锁让程序了解是否还有线程函数没执行完,而且可以做到当所有的线程函数执行完后,程序会立即退出,无需任何等待,提高程序运行的效率。
线程锁的使用比较简单,主要分为4个步骤:创建锁、获取锁、释放锁、检查锁。首先,使用_thread.allocate_lock()函数创建锁对象,然后使用锁对象的acquire()方法获取锁,如果不需要锁,可以使用锁对象的release()方法释放锁,最后通过使用锁对象的locked()方法检查锁是否被释放,如果锁被某个线程锁定,则返回True,否则返回False。例如:
动手练一练:
import time
import _thread
def fun(n, s, lock):
print("第", n, "个线程开始执行时间:", time.ctime())
time.sleep(s)
print("第", n, "个线程结束执行时间:", time.ctime())
lock.release()
def main():
# 创建第一个锁对象(分配锁)
a = _thread.allocate_lock()
# 获取锁,相当于加锁
a.acquire()
# 启动第一个线程,并传入a锁对象
_thread.start_new_thread(fun, (1, 2, a))
# 创建第二个锁对象
b = _thread.allocate_lock()
b.acquire()
_thread.start_new_thread(fun, (2, 4, b))
# 通过while循环和locked()方法判断锁是否全部释放,并退出执行
# 当任何一个锁处于上锁状态,while循环判定为True,程序会一直执行,直到False退出
while a.locked() or b.locked():
# pass不做操作
pass
if __name__ == "__main__":
main()
执行以上代码,输出结果为:
第 1 个线程开始执行时间: Mon Apr 29 08:48:51 2024
第 2 个线程开始执行时间: Mon Apr 29 08:48:51 2024
第 1 个线程结束执行时间: Mon Apr 29 08:48:53 2024
第 2 个线程结束执行时间: Mon Apr 29 08:48:55 2024
上面的例子中,我们在main()主线程函数里面启动两个线程,并创建两个锁对象,在运行两个线程函数之前,先使用acquire()方法获取这两个锁,让锁处于锁定状态,当线程函数执行完毕后,会调用锁对象的release()方法释放锁。通过while循环判断锁是否全部释放,若两个锁都没释放,程序不能停止。从执行的结果可以看到,两个线程可以“同步”地执行,并且两个线程会根据自己线程内的time.sleep()休眠时间执行,直到两个线程函数都执行完毕会自动退出。使用了线程锁可以有效地避免主线程过早或者过晚地退出,而不需要在main()主线程函数里面手动使用time.sleep()暂停等待程序执行完毕。