三.并发编程 (线程锁)

时间:2022-03-13 18:01:33

一 .线程锁( Lock,RLock)

1. GIL(全局解释器锁)

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
 可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。

  Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。
为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。 GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。 在调用任何Python C API之前,要先获得GIL GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作
GIL的影响:
无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
所以,python是无法利用多核CPU实现多线程的。
这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理 有了GIL的存在,同一时刻同一进程中只有一个线程被执行

全局解释器锁 --GIL  :同一个时刻有多个线程 但是只能有一个线程方法CPU(同一时刻同一进程中只有一个线程被执行)

全局解释器锁 --GIL  锁的是什么:
                              锁的是线程

是python语言的问题吗:
                    不是 是Cpython解释器的特性

多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行

注意多线程内部有自己的数据栈 数不共享

描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。 1 .GIL:又叫全局解释器锁,每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题
。它并不是python语言的特性,仅仅是由于历史的原因在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中。
2. https://blog.csdn.net/qq_40808154/article/details/89398076
3. https://blog.csdn.net/weixin_40612082/article/details/82559560
3. https://blog.csdn.net/weixin_43867210/article/details/85716990
 
 

 互斥锁(同步锁)

 锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

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
print(n)   #结果可能为99
# 多个线程抢占资源的情况      同步锁
 
 

 互斥锁(同步锁)

 锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据


from
threading import Thread,Lock import time, os n = 100 def work(): global n lock.acquire() # 在改变数据之前加锁 temp = n time.sleep(0.1) # 模拟IO,没有io因为cpu执行速度太快,结果大部分正确,但为了保证数据安全,最好加上锁 n = temp - 1 lock.release() # 解锁 # with lock: 自动加锁解锁 # temp=n # n=temp-1 if __name__ == '__main__': ti=[] lock = Lock() for i in range(100): # 开启一百个线程 t = Thread(target=work) ti.append(t) t.start() for i in ti: i.join() print(n) print(n) # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放)

待资源访问完后,再调用release方法释放锁
import threading
R=threading.Lock()
R.acquire()
'''
对公共数据的操作
'''
R.release()

2. 互斥锁和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-1 is running
Thread-2 is running
......
Thread-100 is running
主: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 #耗时是多么的恐怖
'''

 3.死锁和递归锁(递归锁来解决死锁)

进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额

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



递归锁
from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
递归锁如同 一串钥匙
可以理解为 一个人拿着拿着一串钥匙开门(意思就是一层一层的往里面开门)
出来也是带着一串一串钥匙出来

                                                                              三.并发编程 (线程锁)

例如: 递归锁

意思有多个人吃面 但是桌子上只有一盘面     面上面有一把锁   叉子上有一把锁  
          (意思要拿到面的锁和叉子的锁  用钥匙去开才能吃到面  吃到了面 把钥匙还回去继续下一个人   )

如果 有一个人拿到 面上的锁   也是吃不不到面

如果 有一个人拿到 叉子锁 也是吃不不到面
死锁
如果 有一个人拿到 面上的锁   也是吃不不到面
如果 有一个人拿到 叉子锁 也是吃不不到面
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 ['张三','李四','王五']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
    

# 执行
张三 抢到了面条
张三 抢到了叉子
张三 吃面
张三 抢到了叉子
李四 抢到了面条
递归锁解决死锁问题
# 例如:
# 意思有多个人吃面 但是桌子上只有一盘面 面上面有一把锁 叉子上有一把锁
# (意思要拿到面的锁和叉子的锁 用钥匙去开才能吃到面 吃到了面 把钥匙还回去继续下一个人 ) 依次有序 变成串行同步
# 如果 有一个人拿到 面上的锁 也是吃不不到面
# 如果 有一个人拿到 叉子锁 也是吃不不到面
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 ['张三','李四','王五']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()

执行
张三 抢到了面条
张三 抢到了叉子
张三 吃面
张三 抢到了叉子
张三 抢到了面条
张三 吃面
李四 抢到了面条
李四 抢到了叉子
李四 吃面
李四 抢到了叉子
李四 抢到了面条
李四 吃面
王五 抢到了面条
王五 抢到了叉子
王五 吃面
王五 抢到了叉子
王五 抢到了面条
王五 吃面
进程已结束,退出代码 0