线程拓展

线程在执行任务的过程中是无序的。然而在实际操作中,有时我们需要两个或多个线程之间按照一定顺序依次执行。这个时候,我们就要想办法控制线程的执行顺序了。

自定义线程

我们创建线程利用的是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就会结束,因此无法执行解锁语句,导致互斥锁一直处于锁定状态,第二个函数想要调用该锁,遇到加锁语句却发现锁处于锁定状态,就会一直等待下去。此种情况即为死锁
因此,要想避免这种情况,一定要记得解锁,并保证程序能够正确运行解锁语句。

猜你喜欢

转载自blog.csdn.net/washing1127/article/details/83418386