Python线程互斥锁

一、线程间共享全局变量

多线程开发的时候共享全局变量会带来资源竞争效果,数据不安全。

import threading
import time

g_num = 0
def test1(num):
    global g_num
    for i in range(num):
        g_num += 1
        print(f"test1--->{g_num}")
def test2(num):
    global g_num
    for i in range(num):
        g_num += 1
        print(f"test2--->{g_num}")
def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))
    t1.start()
    t2.start()
if __name__ == '__main__':
    main()
二、同步异步概念

同步的意思就是协同步调,按预定的先后次序执行。例如你先说完然后我再说。

大家不要将同步理解成一起动作,同步是指协同、协助、互相配合。

例如线程同步,可以理解为线程A和B一块配合工作,A执行到一定程度时要依靠B的某个结果,于是停下来示意B执行,B执行完将结果给A,然后A继续执行。

A强依赖B(对方),A必须等到B的回复,才能做出下一步响应。即A的操作(行程)是顺序执行的,中间少了哪一步都不可以,或者说中间哪一步出错都不可以。

举个例子:

你去外地上学(人生地不熟),突然生活费不够了;此时你决定打电话回家,通知家里转生活费过来,可是当你拨出电话时,对方一直处于待接听状态(即:打不通,联系不上),为了拿到生活费,你就不停的oncall、等待,最终可能不能及时要到生活费,导致你今天要做的事都没有完成,而白白花掉了时间。

异步:

异步则相反,A并不强依赖B,A对B响应的时间也不敏感,无论B返回还是不返回,A都能继续运行;B响应并返回了,A就继续做之前的事情,B没有响应,A就做其他的事情。也就是说A不存在等待对方的概念。

举个例子:

在你打完电话发现没人接听时,猜想:对方可能在忙,暂时无法接听电话,所以你发了一条短信(或者语音留言,亦或是其他的方式)通知对方后便忙其他要紧的事了;这时你就不需要持续不断的拨打电话,还可以做其他事情;待一定时间后,对方看到你的留言便回复响应你,当然对方可能转钱也可能不转钱。但是整个一天下来,你还做了很多事情。 或者说你找室友临时借了一笔钱,又开始happy的上学时光了。

对于多线程共享全局变量计算错误的问题,我们可以使用线程同步来进行解决。

三、互斥锁

当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进入写入操作。从而保证了多线程下数据的安全性。

1.练习一使用互斥锁解决200万次的计算问题。

import threading
import time

g_num = 0
lock = threading.Lock() # 创建一个锁,默认不上锁

def test1(num):
    global g_num
    lock.acquire()
    for i in range(num):
        g_num += 1
    lock.release()
    print(f"test1--->{g_num}")

def test2(num):
    global g_num
    lock.acquire()
    for i in range(num):
        g_num += 1
    lock.release()
    print(f"test2--->{g_num}")

def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))
    t1.start()
    t2.start()

if __name__ == '__main__':
    main()
四、线程间使用非全局变量
from threading import Thread
import threading
import time

def test1():
    name = threading.current_thread().getName() # 获取当前线程的名字
    print(f"----thread name is {name}")
    g_num = 100
    if name == "Thread-1":
        g_num += 1
    # else:
    #     time.sleep(2)
    print(f"----thread is {name}, g_num is {g_num}")


def main():
    t1 = Thread(target=test1)
    t1.start()

    t2 = Thread(target=test1)
    t2.start()

if __name__ == '__main__':
    main()

非全局对于同一个函数来说.可以通过线程的名字来区分.

五、死锁

所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源时,就会造成死锁。
尽管死锁很少发生,但一旦发生就会造成应用的停止响应。
产生死锁的代码:

import threading
import time

lockA = threading.Lock()
lockB = threading.Lock()


class Thread_1(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        self.foo()
        self.bar()

    def foo(self):
        lockA.acquire()
        print(f"{self.name}得到lockA {time.ctime()}")
        lockB.acquire()
        print(f"{self.name}得到lockB {time.ctime()}")

        lockB.release()
        print(f"{self.name}释放lockB {time.ctime()}")
        lockA.release()
        print(f"{self.name}释放lockA {time.ctime()}")

    def bar(self):
        lockB.acquire()
        print(f"{self.name}得到lockB {time.ctime()}")
        lockA.acquire()
        print(f"{self.name}得到lockA {time.ctime()}")

        lockA.release()
        print(f"{self.name}释放lockA {time.ctime()}")
        lockB.release()
        print(f"{self.name}释放lockB {time.ctime()}")


if __name__ == '__main__':
    for i in range(10):
        t = Thread_1()
        t.start()
六、解决死锁办法
1. 递归锁

递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

上面的例子如果使用RLock代替Lock,则不会发生死锁,

二者的区别是:

递归锁可以连续acquire多次,每acquire一次计数器加1,
只要计数不为0,就不能被其他线程抢到。只有计数为0时,才能被其他线程抢到acquire。释放一次计数器-1
而互斥锁只能加锁acquire一次,想要再加锁acquire,就需要release解之前的锁

import threading
import time

lockB = lockA = threading.RLock()

class Thread_1(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        self.foo()
        self.bar()

    def foo(self):
        lockA.acquire()
        print(f"{self.name}得到lockA {time.ctime()}")
        lockB.acquire()
        print(f"{self.name}得到lockB {time.ctime()}")

        lockB.release()
        print(f"{self.name}释放lockB {time.ctime()}")
        lockA.release()
        print(f"{self.name}释放lockA {time.ctime()}")

    def bar(self):
        lockB.acquire()
        print(f"{self.name}得到lockB {time.ctime()}")
        lockA.acquire()
        print(f"{self.name}得到lockA {time.ctime()}")

        lockA.release()
        print(f"{self.name}释放lockA {time.ctime()}")
        lockB.release()
        print(f"{self.name}释放lockB {time.ctime()}")

if __name__ == '__main__':
    for i in range(10):
        t = Thread_1()
        t.start()
2. Semaphore(信号量)

Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

import threading
import time

seamphore = threading.Semaphore(5)

def foo():
    seamphore.acquire()
    time.sleep(2)
    print("ok")
    seamphore.release()
for i in range(10):
    t = threading.Thread(target=foo)
    t.start()
3. condition实现线程通信

threading.Condition() 可以理解为更加高级的锁,比 Lock 和 Rlock 的用法更高级,能处理一些复杂的线程同步问题。threading.Condition() 创建一把资源锁(默认是Rlock),提供 acquire() 和 release() 方法,用法和 Rlock 一致。此外 Condition 还提供 wait()、Notify() 和 NotifyAll() 方法。

wait():线程挂起,直到收到一个 Notify() 通知或者超时(可选参数),wait() 必须在线程得到 Rlock 后才能使用。

Notify() :在线程挂起的时候,发送一个通知,让 wait() 等待线程继续运行,Notify() 也必须在线程得到 Rlock 后才能使用。 Notify(n=1),最多唤醒 n 个线程。

NotifyAll() :在线程挂起的时候,发送通知,让所有 wait() 阻塞的线程都继续运行。

import threading,time

def TestA():
    cond.acquire()
    print('李白:看见一个敌人,请求支援')
    cond.wait()
    print('李白:好的')
    cond.notify()
    cond.release()

def TestB():
    time.sleep(2)
    cond.acquire()
    print('亚瑟:等我...')
    cond.notify()
    cond.wait()
    print('亚瑟:我到了,发起冲锋...')

if __name__=='__main__':
    cond = threading.Condition()
    testA = threading.Thread(target=TestA)
    testB = threading.Thread(target=TestB)
    testA.start()
    testB.start()
    testA.join()
    testB.join()
4. Event实现线程通信

hreading.Event() 原理是在线程中立了一个 Flag ,默认值是 False ,当一个或多个线程遇到 event.wait() 方法时阻塞,直到 Flag 值 变为 True 。threading.Event() 通常用来实现线程之间的通信,使一个线程等待其他线程的通知 ,把 Event 传递到线程对象中。

event.wait() :阻塞线程,直到 Flag 值变为 True

event.set() :设置 Flag 值为 True

event.clear() :修改 Flag 值为 False

event.isSet() : 仅当 Flag 值为 True 时返回

下面这个例子,主线程启动子线程后 sleap 2秒,子线程因为 event.wait() 被阻塞。当主线程醒来后执行 event.set() ,子线程才继续运行,两者输出时间差 2s。

mport threading
import datetime,time

class thread(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name='线程' + threadname)
        self.threadname = int(threadname)

    def run(self):
        event.wait()
        print('子线程运行时间:%s'%datetime.datetime.now())

if __name__ == '__main__':
    event = threading.Event()
    t1 = thread('0')
    #启动子线程
    t1.start()
    print('主线程运行时间:%s'%datetime.datetime.now())
    time.sleep(2)
    # Flag设置成True
    event.set()
    t1.join()
  • threading.active_count():返回当前存活的线程对象的数量

  • threading.current_thread():返回当前线程对象

  • threading.enumerate():返回当前所有线程对象的列表

  • threading.get_ident():返回线程pid

  • threading.main_thread():返回主线程对象

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值