Python程序中的线程操作-锁

Python程序中的线程操作-锁

一、同步锁

1.1多个线程抢占资源的情况

from threading import Thread
import os,time
def work():
    global n
    temp=n
    time.sleep(0.1)
    n=temp-1
if __name__ == '__main__':
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果可能为99

1.1.1对公共数据的操作

import threading
R=threading.Lock()
R.acquire()
'''
对公共数据的操作
'''
R.release()

1.2同步锁的引用

from threading import Thread, Lock
import os, time

def work():
    global n
    lock.acquire()
    temp = n
    time.sleep(0.1)
    n = temp - 1
    lock.release()

if __name__ == '__main__':
    lock = Lock()
    n = 100
    l = []
    for i in range(100):
        p = Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n)  # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

1.3互斥锁与join的区别

# 不加锁:并发执行,速度快,数据不安全
from threading import current_thread, Thread, Lock
import os, time

def task():
    global n
    print('%s is running' % current_thread().getName())
    temp = n
    time.sleep(0.5)
    n = temp - 1

if __name__ == '__main__':
    n = 100
    lock = Lock()
    threads = []
    start_time = time.time()
    for i in range(100):
        t = Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    stop_time = time.time()
    print('主:%s n:%s' % (stop_time - start_time, n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''
# 不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread, Thread, Lock
import os, time

def task():
    # 未加锁的代码并发运行
    time.sleep(3)
    print('%s start to run' % current_thread().getName())
    global n
    # 加锁的代码串行运行
    lock.acquire()
    temp = n
    time.sleep(0.5)
    n = temp - 1
    lock.release()

if __name__ == '__main__':
    n = 100
    lock = Lock()
    threads = []
    start_time = time.time()
    for i in range(100):
        t = Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time = time.time()
    print('主:%s n:%s' % (stop_time - start_time, n))

'''
Thread-2 start to run
Thread-3 start to run
Thread-1 start to run
Thread-6 start to run
Thread-4 start to run
......
Thread-99 start to run
Thread-96 start to run
Thread-100 start to run
Thread-92 start to run
Thread-93 start to run
主:53.294203758239746 n:0
'''

有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊

没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是

start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的

单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.

from threading import current_thread, Thread, Lock
import os, time

def task():
    time.sleep(3)
    print('%s start to run' % current_thread().getName())
    global n
    temp = n
    time.sleep(0.5)
    n = temp - 1

if __name__ == '__main__':
    n = 100
    lock = Lock()
    start_time = time.time()
    for i in range(100):
        t = Thread(target=task)
        t.start()
        t.join()
    stop_time = time.time()
    print('主:%s n:%s' % (stop_time - start_time, n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''

二、死锁与递归锁

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

2.1死锁

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[41m%s 拿到A锁\033[0m' %self.name)

        mutexB.acquire()
        print('\033[42m%s 拿到B锁\033[0m' %self.name)
        mutexB.release()

        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print('\033[43m%s 拿到B锁\033[0m' %self.name)
        time.sleep(2)

        mutexA.acquire()
        print('\033[44m%s 拿到A锁\033[0m' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''

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

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

三、典型问题:科学家吃面

3.1死锁问题

import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','nick','tank']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()

3.2递归锁解决死锁问题

import time
from threading import Thread, RLock

fork_lock = noodle_lock = RLock()

def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    print('%s 吃面' % name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒', 'nick', 'tank']:
    t1 = Thread(target=eat1, args=(name,))
    t2 = Thread(target=eat2, args=(name,))
    t1.start()
    t2.start()

四、信号量Semaphore

同进程的一样

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

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

from threading import Thread,Semaphore
import threading
import time

def task():
    sm.acquire()
    print(f"{threading.current_thread().name} get sm")
    time.sleep(3)
    sm.release()

if __name__ == '__main__':
    sm = Semaphore(5) # 同一时间只有5个进程可以执行。
    for i in range(20):
        t = Thread(target=task)
        t.start()

与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

原文地址:https://www.cnblogs.com/Lin2396/p/11568445.html

时间: 2024-10-29 05:17:31

Python程序中的线程操作-锁的相关文章

120 python程序中的线程操作-锁

一.同步锁 1.1 多个线程抢占资源的情况 from threading import Thread,Lock x = 0 def task(): global x for i in range(200000): x = x+1 # t1 的 x刚拿到0 保存状态 就被切了 # t2 的 x拿到0 进行+1 1 # t1 又获得运行了 x = 0 +1 1 # 这就产生了数据安全问题. if __name__ == '__main__': # 使用的是操作系统的原生线程. t1 = Thread

Python程序中的线程操作-concurrent模块

Python程序中的线程操作-concurrent模块 一.Python标准模块--concurrent.futures 官方文档:https://docs.python.org/dev/library/concurrent.futures.html 二.介绍 concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecutor:进程池,提供异步调用 两者都实现相同的接口,该接口由抽象Execut

Python程序中的线程操作(线程池)-concurrent模块

目录 Python程序中的线程操作(线程池)-concurrent模块 一.Python标准模块--concurrent.futures 二.介绍 三.基本方法 四.ProcessPoolExecutor 五.ThreadPoolExecutor 六.map的用法 七.回调函数 Python程序中的线程操作(线程池)-concurrent模块 一.Python标准模块--concurrent.futures 官方文档:https://docs.python.org/dev/library/con

118 python程序中的线程操作-创建多线程

一.python线程的模块 1.1 thread和threading模块 thread模块提供了基本的线程和锁的支持 threading提供了更高级别.功能更强的线程管理的功能. 1.2 Queue模块 Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构. 1.3注意模块的选择 避免使用thread模块 因为更高级别的threading模块更为先进,对线程的支持更为完善 而且使用thread模块里的属性有可能会与threading出现冲突: 其次低级别的thread模块的同

119 python程序中的线程操作-守护线程

一.守护线程 无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁.需要强调的是:运行完毕并非终止运行. 对主进程来说,运行完毕指的是主进程代码运行完毕 对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕 1.1 详解 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束. 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被

122 python程序中的线程操作-concurrent模块

一.concurrent模块的介绍 concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecutor:进程池,提供异步调用 ProcessPoolExecutor 和 ThreadPoolExecutor:两者都实现相同的接口,该接口由抽象Executor类定义. 二.基本方法 submit(fn, *args, **kwargs):异步提交任务 map(func, *iterables, t

121 python程序中的线程操作-队列queue

一.线程队列 queue队列:使用方法同进程的Queue一样 如果必须在多个线程之间安全地交换信息时,队列在线程编程中尤其有用. 重要: q.put():往队列里面放值,当参数block=Ture的时候,timeout参数将会有作用,当队列已经满了的时候,在往里面放值时,block为True程序将会等待timeout的时间,过了时间程序会报错,block如果为Flase时,程序不会等待直接报错 q.get():从队列里面取值,当参数block=Ture的时候,timeout参数将会有作用,当队列

121 Python程序中的线程操作-线程定时器

目录 一.线程定时器 二.用法 一.线程定时器 线程定时器也是定时器,就是定时之后开启一条线程 二.用法 ''' 线程定时器,就是规定时间后开启一条线程 ''' def task(): print('线程执行了') time.sleep(2) print('线程结束了') t = Timer(4,task) # 间隔时间, 功能函数 t.start() 原文地址:https://www.cnblogs.com/XuChengNotes/p/11553061.html

Python程序中的进程操作-进程池(multiprocess.Pool)

Python程序中的进程操作-进程池(multiprocess.Pool) 一.进程池 为什么要有进程池?进程池的概念. 在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务.那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间.第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率.因此我们不能无限制的根据任务开启或者结束进程.那么我们要怎么做呢? 在这里,要给大家介