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:
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 里的线程安全、原子操作 》
|