线程在执行任务的过程中是无序的。然而在实际操作中,有时我们需要两个或多个线程之间按照一定顺序依次执行。这个时候,我们就要想办法控制线程的执行顺序了。
自定义线程
我们创建线程利用的是threading.Thread
类,该类可以创造出一个线程来供一个函数运行。由此以出发点,我们可以创建一个类似于这个类的新类,来供两个或多个函数有序运行。
自定义线程代码:
import threading
# 自定义线程类
class MyThread(threading.Thread):
# 通过构造方法取接收任务的参数
def __init__(self, info1, info2):
# 调用父类的构造方法
super(MyThread, self).__init__()
self.info1 = info1
self.info2 = info2
# 定义自定义线程相关的任务
def test1(self):
print(self.info1)
def test2(self):
print(self.info2)
# 通过run方法执行相关任务
def run(self):
self.test1()
self.test2()
# 创建自定义线程
my_thread = MyThread("测试1", "测试2")
# 启动
my_thread.start()
此方法虽是可以令多个函数有序执行,但其实是有些取巧的把多个程序写到了一个线程中。不能完全算作多个线程依次执行。
多线程共享全局变量问题
多线程之间可以共享全局变量,但当多个线程同时对一个全局变量进行操作时,有可能就会产生一些意料之外的问题。如下代码:
import threading
a = 0
def aa():
global a
for i in range(10000000):
a += 1
print('aa', a)
def bb():
global a
for i in range(10000000):
a += 1
print('bb', a)
at = threading.Thread(target=aa)
bt = threading.Thread(target=bb)
at.start()
bt.start()
程序运行结果(非固定):
aa 12655691
bb 12676419
原因分析:
一个线程在使用全局变量时需要经历:取值、运算、赋值
线程需要先从全局变量处取得其值,然后利用自身函数进行运算,运算之后把结果重新赋给全局变量。这个时候就容易产生问题。
理想情况:
a是全局变量,初始值等于0
线程一取得了其值0,经过运算0+10,又重新将a重新赋值成10
线程二又取得其值10,经过运算10+5,又重新将a重新赋值成15
。。。
意外情况:
a是全局变量,初始值等于0
线程一取得了其值0,正在运算0+10,还未来得及将a重新赋值
线程二又取得了其值0,并进行运算0+5
线程一运算结束,把a赋值成了10
线程二运算结束,把a赋值成了5
多线程共享全局变量出现错误的解决办法
保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。
方法1: 线程等待
线程等待很简单即在启动某个线程后调用.join()
方法,来使其他线程进入等待状态。如:
import threading
a = 0
def aa():
global a
for i in range(10000000):
a += 1
print('aa', a)
def bb():
global a
for i in range(10000000):
a += 1
print('bb', a)
at = threading.Thread(target=aa)
bt = threading.Thread(target=bb)
at.start()
# 调用.join()方法,使其他线程进入等待状态。
at.join()
bt.start()
某线程调用
.join()
后,其他线程会等待着该线程运行完再继续运行。
方法2: 互斥锁
互斥锁即创建一把同时只能被一个线程使用的锁,来控制着同时只能有一个线程执行任务。需要调用.Lock()
方法来创建一把锁。如:
import threading
a = 0
# 创建一把互斥锁
c = threading.Lock()
def aa():
global a
# 加锁
c.acquire()
for i in range(10000000):
a += 1
print('aa', a)
# 解锁
c.release()
def bb():
global a
# 加锁
c.acquire()
for i in range(10000000):
a += 1
print('bb', a)
# 解锁
c.release()
at = threading.Thread(target=aa)
bt = threading.Thread(target=bb)
at.start()
bt.start()
可见线程同步够达到我们想要的结果,但是无论是线程等待还是线程锁,都相当于把多线程变成了单线程执行任务,不免会降低效率。
同时,使用互斥锁的时候还应该注意到使用前要加锁,使用后要解锁。
如果未解锁或者由于程序不严谨导致无法解锁,就会进入死锁状态,造成程序阻塞无法继续运行。如:
import threading
a = 0
c = threading.Lock()
def aa():
global a
c.acquire()
for i in range(10000000):
a += 1
print('aa', a)
return
# 函数遇到return,将会结束,所以无法运行到下边的解锁语句
c.release()
def bb():
global a
c.acquire()
for i in range(10000000):
a += 1
print('bb', a)
c.release()
at = threading.Thread(target=aa)
bt = threading.Thread(target=bb)
at.start()
bt.start()
以上函数中,当第一个函数运行过程中遇到return就会结束,因此无法执行解锁语句,导致互斥锁一直处于锁定状态,第二个函数想要调用该锁,遇到加锁语句却发现锁处于锁定状态,就会一直等待下去。此种情况即为死锁。
因此,要想避免这种情况,一定要记得解锁,并保证程序能够正确运行解锁语句。