python并发编程-同步锁(python并发和并行)

需要注意的点:

1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来

2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

GIL VS Lock

Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?


首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

然后,我们可以得出结论:保护不同的数据就应该加不同的锁。

最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,

前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),

后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock


过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限

  线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果

  既然是串行,那我们执行

  t1.start()

  t1.join

  t2.start()

  t2.join()

  这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。


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

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

import threading

R=threading.Lock()

R.acquire()
'''
对公共数据的操作
'''
R.release()


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.100个线程去抢GIL锁,即抢执行权限

2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()

3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL

4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程 GIL锁与互斥锁综合分析


可能有疑问:既然加锁会让运行变成串行,那么我在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 #耗时是多么的恐怖
'''

相关文章

python 锁Lock功能及多线程程序锁的使用和常见功能示例

锁(Lock)是Python中的一个同步原语,用于线程之间的互斥访问。它可以用来保护共享资源,确保在任意时刻只有一个线程可以访问共享资源,从而避免多线程并发访问引发的数据竞争和不一致性。下面分别详细说...

一文扫盲!Python 多线程的正确打开方式

一、多线程:程序世界的 "多面手"(一)啥是多线程?咱先打个比方,你去餐厅吃饭,一个服务员同时接待好几桌客人,每桌客人就是一个 "线程",服务员同时处理多桌事务就是 &...

python 多线程程序加锁、解锁、锁应用场景示例

锁(Lock)是Python中的一个同步原语,用于线程之间的互斥访问。它可以用来保护共享资源,确保在任意时刻只有一个线程可以访问共享资源,从而避免多线程并发访问引发的数据竞争和不一致性。下面分别详细说...

Python中的“锁”艺术:解锁Lock与RLock的秘密

Python中的“锁”艺术:解锁Lock与RLock的秘密引言随着计算机性能的不断提升以及多核处理器的普及,多线程编程已成为现代软件开发不可或缺的一部分。然而,当多个线程试图同时修改同一份数据时,就可...

24-2-Python多线程-线程操作(python多线程怎么用)

2-线程操作在Python程序中,可以通过“_thread”和“threading(推荐使用)”这两个模块来处理线程。在Python 3程序中,thread模块已废弃。可以使用 threading 模...

Python 如何通过 threading 模块实现多线程。

先熟悉下相关概念多线程是并发编程的一种方式,多线程在 CPU 密集型任务中无法充分利用多核性能,但在 I/O 操作(如文件读写、网络请求)等待期间,线程会释放 GIL,此时其他线程可以运行。GIL是P...