Python----线程(Threading)---线程安全问题 & 互斥锁 & 队列

1.线程安全的问题?

因为线程之间存在资源竞争的情况,也就是说一个全局变量经过多个线程的共同操作,最终的结果出现异常情况,这就是线程安全的问题

num = 0


def sum_one(quantity):
    global num
    for index in range(quantity):
        num += 1
    return num


t1 = Thread(target=sum_one, args=(1000000,))
t2 = Thread(target=sum_one, args=(2000000,))
t3 = Thread(target=sum_one, args=(3000000,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print("num-------->", num)


# 结果
num--------> 4565212

以上频率,如果降低到万级别或更低频率其实结果是正确的。由于存在这样的偶然性,所以认为存在线程间安全的问题。那产生的原因是什么呢?如何解决呢?



2.线程安全产生的根源?

为了保证使用共享数据的并发编程能正确、高效协作,对于一个好的解决方案,需要满足以下四个条件:

  • 任何俩个线程不能同时处于临界区
  • 不应对cpu的速度和数量做任何假设
  • 临界区外运行的线程不能阻塞其他线程
  • 不得使线程无限期等待进入临界区

在这里插入图片描述

以上内容大白话是:

  • 1.当某个线程执行一个函数的时候(赋值或其他操作),可以理解为执行状态(临界区)。这执行状态如果分为多步骤,中途需要阻塞其他线程的操作,直到这个步骤结束。
  • 2.如果执行的过程是“原子性”的,只有一步就操作完,则不会出现赋值错乱情况。


【错误过程的解读?】

在这里插入图片描述

(1)自身赋值的过程分为2步,第一步拿到g_num的值并加1, 第二部把相加之后的值赋值为g_num。

(2)因线程之间的资源的竞争问题,执行的顺序是随机的。如果t1先拿到原始值0,加完之后为1,但此时t2也拿到原始值0,加完之后也为1。

(3)t1执行第二步骤,存储g_num的值为1。由于t2和t1之间没有通讯,t2认为g_num的值还是最初的值,就把t2计算的结果1,再次赋值为g_num。

(4)最终对g_num赋值了2次,都是1,结果导致最终计算的值偏小。



3.原子操作?

原子操作(atomic operation),指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会切换到其他线程。它有点类似数据库中的 事务。


【Python3中常见的原子操作?】

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()


【Python3中非原子操作?】

i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1



4.线程间通讯----互斥锁?

  • 通过线程锁,可以解决线程安全的问题。原理就是,把不是原子操作的步骤,模拟出来原子操作(在某线程多步骤执行的时候,其他线程都阻塞)。
  • 通过线程锁, 就可以实现了线程之间的通讯

【案例一】:多个线程,每个线程执行的次数通过参数传递


num = 0

mutex = threading.Lock()

# 方式一:
def sum_one(quantity):
    global num
    for index in range(quantity):
        with mutex:		# mutex自动控制锁的释放
            num += 1
    return num


# 方式二:
def sum_one(quantity):
    global num
    for index in range(quantity):
        mutex.acquire()		# 上锁
        num += 1
        mutex.release()		# 释放锁
    return num


t1 = Thread(target=sum_one, args=(1000000,))
t2 = Thread(target=sum_one, args=(2000000,))
t3 = Thread(target=sum_one, args=(3000000,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print("num-------->", num)



# 结果
num--------> 6000000


【案例二】:多个线程,所有线程执行的次数固定

number = 0
quantity = 500000

'''创建一个互斥锁,默认是没有上锁的'''
mutex = threading.Lock()

# 方式一:
def sum_func(param):
    global number, quantity
    while True:
        '''上锁'''
        mutex.acquire()
        if quantity > 0:
            print("num & quantity------->", number, quantity)
            number += 1
            quantity -= 1
            print("[%s] number=%d" % (param, number))
            mutex.release()
        else:
            print("===========================>>>>")
            mutex.release()
            break


# 方式二:
def sum_func(param):
    global number, quantity
    while True:
        '''上锁'''
        with mutex:
            if quantity > 0:
                number += 1
                quantity -= 1
            else:
                print("===========================>>>>")
                break


def main():
    t1 = threading.Thread(target=sum_func, args=("t_1",))  # 加上要传递的参数,元组类型
    t2 = threading.Thread(target=sum_func, args=("t_2",))
    t3 = threading.Thread(target=sum_func, args=("t_3",))
    t1.start()
    t2.start()
    t3.start()
    t1.join()
    t2.join()
    t3.join()
    print("number-------->", number)


if __name__ == '__main__':
    main()


# 结果(打印相对很费时间,过程数据如下)

num & quantity-------> 499996 4
[t_3] number=499997
num & quantity-------> 499997 3
[t_2] number=499998
num & quantity-------> 499998 2
[t_2] number=499999
num & quantity-------> 499999 1
[t_2] number=500000
===========================>>>>
===========================>>>>
===========================>>>>
number--------> 500000



5.线程间通讯----队列?

  • 线程间通讯,还会使用到队列,因为队列的写入读取。注意啦,只有队列的写入、读取是原子的。
  • 如果读完信息进行赋值操作,这个赋值步骤还是需要添加锁的!!!

from queue import Queue

shopping_cart = Queue(maxsize=10)


def produce_product(quantity):
    for index in range(quantity):
        product_name = "product_" + str(index)
        shopping_cart.put(product_name)
        print("produce_product--------->", product_name, "\n")
        time.sleep(0.2)


def consume_product():
    while True:
        try:
            product_name = shopping_cart.get(timeout=1)
            time.sleep(0.1)
            print("consume_product----------->", product_name, "\n")
        except Exception as e:
            print("consume_product----------->finish!!!", e, "\n")
            break


def main():
    t1 = threading.Thread(target=produce_product, args=(10,))  # 加上要传递的参数,元组类型
    t2 = threading.Thread(target=consume_product)
    t1.start()
    t2.start()
    t1.join()
    t2.join()


if __name__ == '__main__':
    main()

# 结果
produce_product---------> product_0 
consume_product-----------> product_0 
produce_product---------> product_1 
consume_product-----------> product_1 
......
produce_product---------> product_9 
consume_product-----------> product_9 
consume_product----------->finish!!!  




参考博文:《通俗易懂:说说 Python 里的线程安全、原子操作 》

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello-alien

您的鼓励,是我最大的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值