044.Python线程的数据安全

线程的数据安全

1 数据混乱现象

from threading import Thread,Lock
num = 0
lst = []


def func1():
        global num
        for i in range(100000):
                num -= 1

def func2():
        global num
        for i in range(100000):
                num += 1
for i  in range (10):
# 启动线程1
        t1 = Thread(target=func1)
        t1.start()
# 启动线程2
        t2 = Thread(target=func2)
        t2.start()
        lst.append(t1)
        lst.append(t2)

for i in lst:
        i.join()
print("主线程执行结束")
print(num)

执行

由于共享同一份,会出现数据混乱,如下结果

[root@node10 python]# python3 test.py
主线程执行结束
173624
[root@node10 python]# python3 test.py
主线程执行结束
131875
[root@node10 python]# python3 test.py
主线程执行结束
-4279
[root@node10 python]# python3 test.py
主线程执行结束
-93587
[root@node10 python]# python3 test.py
主线程执行结束
145643

这是因为线程是并发执行,同同一时间会有多个线程拿到同一个数据进行计算,然后再放回去,导致一个数字被多个线程修改,结果不准确

2 引入锁机制

from threading import Thread,Lock
num = 0
lst = []


def func1(lock):
        global num
        for i in range(100000):
                #上锁修改数据
                lock.acquire()
                num -= 1
                #解锁释放
                lock.release()

def func2(lock):
        global num
        for i in range(100000):
                #使用with,自动上锁和解锁
                with lock:
                        num += 1
lock = Lock()
for i  in range (10):
# 启动线程1
        t1 = Thread(target=func1,args=(lock,))
        t1.start()
# 启动线程2
        t2 = Thread(target=func2,args=(lock,))
        t2.start()
        lst.append(t1)
        lst.append(t2)

for i in lst:
        i.join()
print("主线程执行结束")
print(num)

执行

[root@node10 python]# python3 test.py
主线程执行结束
0

结果正确,但是消耗的时间毕竟长,但是可以换取数据正确

3 信号量

import time,random,os

def func(i,sem):
        time.sleep(random.uniform(0.1,1))
        with sem:
                print (i)
                print (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
                time.sleep(random.uniform(3,6))


sem = Semaphore(5)
for i in range (20):
        Thread(target = func,args=(i,sem)).start()

执行

[root@node10 python]# python3 test.py
3
2020-02-23 03:55:24
5
2020-02-23 03:55:24
14
2020-02-23 03:55:24
18
2020-02-23 03:55:24
6
2020-02-23 03:55:24
0
2020-02-23 03:55:27
2
2020-02-23 03:55:29
15
2020-02-23 03:55:29
1
2020-02-23 03:55:29
19
2020-02-23 03:55:30
12
2020-02-23 03:55:32
4
2020-02-23 03:55:32
17
2020-02-23 03:55:34
16
2020-02-23 03:55:34
13
2020-02-23 03:55:35
7
2020-02-23 03:55:36
10
2020-02-23 03:55:36
8
2020-02-23 03:55:39
9
2020-02-23 03:55:39
11
2020-02-23 03:55:40

信号量就相当于同时可以上多把锁

死锁,递归锁,互斥锁

4 死锁现象

模拟一个俱乐部玩枪,两把枪,两盒子弹,四个人同时抢,只有同时抢到子弹和枪的人,才能玩枪

初步逻辑

from threading import Thread,Lock
import time
#首先创建两把锁
gun = Lock()
bullet = Lock()
#定义第一种,先抢到枪,再抢到子弹
def play1(name):
        #抢到枪,上锁
        gun.acquire()
        print("%s拿到枪"%(name))
        #抢到子弹上锁
        bullet.acquire()
        print ("%s抢到子弹"%(name))
        print ("开枪玩一会")

        time.sleep(0.5)
        #放下枪,解锁
        gun.release()
        print("%s放下枪"%(name))
        #放下子弹,解锁
        bullet.release()
        print("%s放下子弹"%(name))

#定义第二种,先抢到子弹,再抢到枪
def play2(name):
        #抢到子弹上锁
        bullet.acquire()
        print ("%s抢到子弹"%(name))
         #抢到枪,上锁
        gun.acquire()
        print("%s拿到枪"%(name))
        print ("开枪玩一会")
        time.sleep(0.5)
        #放下子弹,解锁
        bullet.release()
        print("%s放下子弹"%(name))
        #放下枪,解锁
        gun.release()
        print("%s放下枪"%(name))


name_lst1 = ["John","Jim"]
name_lst2 = ["Tom","Jerry"]
for name  in  name_lst1:
        Thread(target=play1,args=(name,)).start()
for name  in  name_lst2:
        Thread(target=play2,args=(name,)).start()

这种情况就会有死锁现象

多次执行,结果如下

[root@node10 python]# python3 test.py
John拿到枪
Tom抢到子弹
#阻塞,是因为John和Tom同时上了枪锁和子弹锁,但是都没有解锁,造成死锁
[root@node10 python]# python3 test.py
John拿到枪
John抢到子弹
开枪玩一会
John放下枪
John放下子弹
Jim拿到枪
Tom抢到子弹
#阻塞,这次是John顺利的玩一局,但是到Jim和Tom同时抢到枪和子弹,上锁,但是没有解锁,造成死锁
[root@node10 python]# python3 test.py
John拿到枪
John抢到子弹
开枪玩一会
John放下枪
John放下子弹
Tom抢到子弹
Jim拿到枪
#阻塞

5 递归锁介绍

上几把锁,解几把锁

from threading import Thread,RLock
rlock = RLock()
def func(name):
        rlock.acquire()
        print(name,1)
        rlock.acquire()
        print(name,2)
        rlock.acquire()
        print(name,3)
        rlock.release()
        rlock.release()
        rlock.release()

for i in range(10):
        t = Thread(target=func,args=("name%s" % (i),) )
        t.start()

print("程序执行结束")

执行

[root@node10 python]# python3 ceshi.py
name0 1
name0 2
name0 3
name1 1
name1 2
name1 3
name2 1
name2 2
name2 3
name3 1
name3 2
name3 3
程序执行结束
name4 1
name4 2
name4 3
name5 1
name5 2
name5 3
name6 1
name6 2
name6 3
name7 1
name7 2
name7 3
name8 1
name8 2
name8 3
name9 1
name9 2
name9 3

6 利用递归锁,解决死锁现象

临时用于快速解决服务器崩溃死锁的问题,用递归锁应急问题

from threading import Thread,RLock
import time
#首先创建递归锁
gun=bullet = RLock()
#定义第一种,先抢到枪,再抢到子弹
def play1(name):
        #抢到枪,上锁
        gun.acquire()
        print("%s拿到枪"%(name))
        #抢到子弹上锁
        bullet.acquire()
        print ("%s抢到子弹"%(name))
        print ("开枪玩一会")

        time.sleep(0.5)
        #放下枪,解锁
        gun.release()
        print("%s放下枪"%(name))
        #放下子弹,解锁
        bullet.release()
        print("%s放下子弹"%(name))

#定义第二种,先抢到子弹,再抢到枪
def play2(name):
        #抢到子弹上锁
        bullet.acquire()
        print ("%s抢到子弹"%(name))
         #抢到枪,上锁
        gun.acquire()
        print("%s拿到枪"%(name))
        print ("开枪玩一会")
        time.sleep(0.5)
        #放下子弹,解锁
        bullet.release()
        print("%s放下子弹"%(name))
        #放下枪,解锁
        gun.release()
        print("%s放下枪"%(name))


name_lst1 = ["John","Jim"]
name_lst2 = ["Tom","Jerry"]
for name  in  name_lst1:
        Thread(target=play1,args=(name,)).start()
for name  in  name_lst2:
        Thread(target=play2,args=(name,)).start()

执行

[root@node10 python]# python3 test.py
John拿到枪
John抢到子弹
开枪玩一会
John放下枪
John放下子弹
Jim拿到枪
Jim抢到子弹
开枪玩一会
Jim放下枪
Jim放下子弹
Tom抢到子弹
Tom拿到枪
开枪玩一会
Tom放下子弹
Tom放下枪
Jerry抢到子弹
Jerry拿到枪
开枪玩一会
Jerry放下子弹
Jerry放下枪

执行成功,神奇

7 使用互斥锁解决

从语法上来看,锁是可以互相嵌套的,但是不要使用

上一次锁,就对应解开一把锁,形成互斥锁

拿枪和拿子弹是同时的,上一把锁就够了,不要分开上锁,也不要去写锁的嵌套,容易死锁

from threading import Thread,Lock
import time
#首先创建递归锁
mylock = Lock()
#定义第一种,先抢到枪,再抢到子弹
def play1(name):
        #抢到枪和子弹,上锁
        mylock.acquire()
        print("%s拿到枪"%(name))
        print ("%s抢到子弹"%(name))

        print ("开枪玩一会")
        time.sleep(0.5)
        #放下枪,和子弹解锁
        print("%s放下枪"%(name))
        print("%s放下子弹"%(name))
        mylock.release()

#定义第二种,先抢到子弹,再抢到枪
def play2(name):
        #抢到枪子弹上锁
        mylock.acquire()
        print ("%s抢到子弹"%(name))
        print("%s拿到枪"%(name))

        print ("%s开枪玩一会"%(name))
        time.sleep(0.5)
        #放枪下子弹,解锁
        print("%s放下子弹"%(name))
        print("%s放下枪"%(name))
        mylock.release()


name_lst1 = ["John","Jim"]
name_lst2 = ["Tom","Jerry"]
for name  in  name_lst1:
        Thread(target=play1,args=(name,)).start()
for name  in  name_lst2:
        Thread(target=play2,args=(name,)).start()

执行

[root@node10 python]# python3 test.py
John拿到枪
John抢到子弹
开枪玩一会
John放下枪
John放下子弹
Jim拿到枪
Jim抢到子弹
开枪玩一会
Jim放下枪
Jim放下子弹
Tom抢到子弹
Tom拿到枪
Tom开枪玩一会
Tom放下子弹
Tom放下枪
Jerry抢到子弹
Jerry拿到枪
Jerry开枪玩一会
Jerry放下子弹
Jerry放下枪

完成

 

你可能感兴趣的:(044.Python线程的数据安全)