并发编程--一堆锁,GIL,同步异步,Event事件

目录

  • 一堆锁

    • 死锁现象(*****
    • 递归锁 RLock (了解)
    • 信号量 (了解)
  • GIL(*****
    • 什么时GIL锁
    • 为什么需要GIL锁
    • Cpython解释器与GC的问题
    • GIL锁带来的问题
    • 多线程与多进程性能对比
  • 进程池与线程池
  • 同步异步(*****
  • Event事件

一堆锁

死锁现象(*****

? 死锁指的是,某个资源被占用之后,一直得不到释放,导致其他需要这个资源的线程进入阻塞状态

  • 产生死锁的情况

    1. 对同一把互斥锁,进行了多次加锁
    2. 一个共享资源,在访问时必须具备多把锁,但是这些锁被不同的线程或进程所持有,这样会导致相互等待对方释放,从而程序卡死

      解决方案:

      1. 按照相同的顺序进行抢锁
      2. 给抢锁加上超时,如果超时就放弃抢锁

递归锁 RLock (了解)

? 与普通的互斥锁相同点在于,在多线程之间有互斥的效果但是同一个线程中,递归锁可以多次加锁,执行多次acquire

? 同一个线程必须保证 加锁的次数和解锁的次数相同 其它线程才能够抢到这把锁

? RLock只是解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题

# 同一把RLock 多次acquire
#l1 = RLock()
#l2 = l1

# 不同的RLock 依然会锁死
#l1 = RLock()
#l2 = RLock()

def task():
    l1.acquire()
    print(threading.current_thread().name,"拿到了筷子")
    time.sleep(0.1)
    l2.acquire()
    print(threading.current_thread().name, "拿到了盘子")

    print("吃饭")
    l1.release()
    l2.release()

def task2():
    l2.acquire()
    print(threading.current_thread().name, "拿到了盘子")

    l1.acquire()
    print(threading.current_thread().name,"拿到了筷子")

    print("吃饭")
    l2.release()
    l1.release()

t1 = Thread(target=task)
t1.start()
t2 = Thread(target=task2)
t2.start()

信号量 (了解)

? 可以用来限制同时并发执行公共代码的线程数量

? 如果限制数量为1,则和普通的互斥锁一样

from threading import Thread, Semaphore, currentThread
import time

s = Semaphore(3)   # 一次只能有3个线程占用
def task():
    s.acquire()
    time.sleep(1)
    print(currentThread().name)
    s.release()

for i in range(10):
    Thread(target=task).start()

? 注意:信号量不是用来解决安全问题的 而是用于限制最大的并发量

GIL(*****

什么时GIL锁

'''
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
'''

'''
在CPython中,全局解释器锁(global interpreter lock, GIL)是一个互斥体,它防止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经逐渐依赖于它强制执行的保证。)
'''

GIL本质是一把互斥锁,但GIL锁住的是解释器级别的数据

自定义锁,锁的是解释器以外的共享资源,例如:硬盘上的文件 控制台,对于这种不属于解释器的数据资源就应该自己加锁处理

为什么需要GIL锁

? python程序本身只是一串字符,只有通过python解释器运行之后才具有意义,但是在一个python程序中python解释器只有一个,所有代码都是由它来执行的,当多个线程都需要执行代码时,就会产生线程安全问题。

? 如果我们不开线程,是否就没有问题了?

Cpython解释器与GC的问题

GC线程又叫垃圾回收机制

在python中,解释器会自动帮我们进行垃圾回收,这也是需要开启一个线程来进行的,也就是说,当我们没有开启子线程的时候,内部还是有多个线程在执行。所以这就会带来线程安全问题

例如:

? 线程a要定义一个变量 b,首先申请一块空内存,然后把数据装进去,此时变量b的引用计数为0,最后才会将引用计数加一

? 但是,如果在刚把数据装进去的时候,CPU切换到GC线程,GC线程就会把变量b当作垃圾进行回收

GIL锁带来的问题

GIL锁本质还是一把互斥锁,所以就会降低程序运行效率

  • 没有解决办法,只有尽可能的避免GIL带来的影响

    1. 使用多线程实现并行,更好的利用多核CPU
    2. 对任务进行区分
      1. 计算密集型

        基本没有IO操作,大部分时间都是在进行计算,例如人脸识别,图像处理

        由于线程不能并行,应该使用多进程,将任务分给不同的CPU进行

      2. IO密集型

        计算任务非常少,大部分时间都在等待IO操作

        由于网络IO速度对比CPU处理速度非常慢,多线程并不会造成太大的影响,另外如有大量客户端连接服务,进程根本开不起来,只能用多线程

多线程与多进程性能对比

加锁主要就是为了解决线程安全问题,但这也导致了Cpython中,多线程只能并发而不能并行

python的优势:

  1. python是一门语言,GIL是Cpython解释器的问题 ,Jpython,pypy 并没有
  2. 如果是单核CPU,GIL不会造成任何影响
  3. 由于目前大多数程序都是基于网络的,网络速度对比CPU是非常慢的,导致即使多核CPU也无法提高效率
  4. 对于IO密集型任务,不会有太大的影响
  5. 如果没有这把锁,我们程序猿将必须自己来解决安全问题

性能测试 :

  1. 计算密集型任务

    from multiprocessing import Process
    from threading import  Thread
    import time
    # # 计算密集型任务
    
    def task():
        for i in range(100000000):
            1+1
    
    if __name__ == '__main__':
        start_time = time.time()
        ps = []
        for i in range(5):
            # p = Process(target=task)   # 4.420854806900024
            p = Thread(target=task)   # 20.043513298034668
            p.start()
            ps.append(p)
    
        for i in ps:i.join()
        print("共耗时:",time.time()-start_time)
  2. IO密集型
    from multiprocessing import Process
    from threading import  Thread
    import time
    # IO密集型任务
    
    def task():
        for i in range(100):
            with open(r"1.死锁现象.py", 'r', encoding="utf-8") as fr:
                fr.read()
    
    if __name__ == '__main__':
        start_time = time.time()
    
        ps = []
        for i in range(10):
            # p = Process(target=task)   # 1.1040928363800049
            p = Thread(target=task)   # 0.09919905662536621
            p.start()
            ps.append(p)
    
        for i in ps:i.join()
        print("共耗时:",time.time()-start_time)

进程池与线程池

池是一个容器,可以用来装一些东西,但池也是有容量限制的,不可能无限制的装东西

进程池和线程池就是用来装进程和线程的容器

进程池和线程池的好处:

  1. 可以避免频繁的创建和销毁(进程/线程)来的资源开销
  2. 可以限制同时存在的线程数量,以保证服务器不会应为资源不足而导致崩溃
  3. 帮我们管理了线程的生命周期
  4. 管理了任务的分配

进程池与线程池的使用

  1. 线程池

    from concurrent.futures import ThreadPoolExecutor
    from threading import enumerate,currentThread
    
    # 创建一个线程池   指定最多可以容纳两个线程
    pool = ThreadPoolExecutor(2)
    
    def task():
        print(currentThread().name)
    
    # 提交任务到池子中
    pool.submit(task)
    pool.submit(task)
    
    print(enumerate())
  2. 进程池
    import os
    import time
    from concurrent.futures import ProcessPoolExecutor
    
    # 创建一个进程池, 指定最多可以容纳两个线程
    def task():
        time.sleep(1)
        print(os.getpid())
    
    if __name__ == '__main__':
        pool = ProcessPoolExecutor(2)
        pool.submit(task)
        pool.submit(task)
        pool.submit(task)

注意:如果进程不结束,池子里面的进程或线程也是一直存活的

同步异步(*****

异步同步指的是提交任务的方式

同步:是指提交任务后,必须需要等待任务完成之后才能进行下一个任务

异步:是指提交任务后,不需要等待任务完成,继续进行下一个任务

异步效率高于同步,但异步任务将导致一个问题,就是任务的发起方不知道任务何时处理完毕

解决办法:

  1. 轮询:每隔一段时间询问一次

    ? 结果:效率低,无法及时获取结果

  2. 异步回调:让任务的执行方主动通知

    ? 结果:可以及时拿到任务的结果

案例:

from threading import Thread

# 具体的任务
def task(callback):
    print('run')
    for i in range(100000000):
        1 + 1
    callback(True)

# 回调函数,参数为任务的结果
def finished(res):
    if res:
        print('任务完成')
    else:
        print('任务失败')

print('start...')
t = Thread(target=task,args=(finished,))
t.start()
print('over')

? 线程池中回调的使用

from concurrent.futures import ThreadPoolExecutor

def task(num):
    time.sleep(1)
    print(num)
    return "hello python"

def callback(obj):
    print(obj.result())

pool = ThreadPoolExecutor()
res = pool.submit(task,123)
res.add_done_callback(callback)
print("over") 

Event事件

Event用于线程间状态同步,

执行状态:指的是程序运行到哪一步,此时的状态

执行结果:程序运行完了得到的结果

如果我们要拿到执行结果,可以采用异步回调

Event事件的本质就是一个标志,True or False

Evevt里面包含了一个wait函数,可以阻塞当前线程,直到状态由False变为True

from threading import Thread, Event
import time

e = Event()
# is_bool = False

def start_server():
    # global is_bool
    print('starting server.....')
    time.sleep(3)
    print('server started')
    # is_bool = True
    e.set()

def connect_server():
    e.wait()
    if e.is_set():
        print('连接服务器成功')
    # while True:
    #     if is_bool:
    #         print('连接服务器成功')
    #         break
    #     else:
    #         print('连接服务器失败')
    #
    #     time.sleep(0.1)

t1 = Thread(target=start_server)
t2 = Thread(target=connect_server)

t1.start()
t2.start()

原文地址:https://www.cnblogs.com/Hades123/p/11158948.html

时间: 2024-11-08 08:47:51

并发编程--一堆锁,GIL,同步异步,Event事件的相关文章

Java并发编程:锁的释放

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

Java并发编程:线程的同步

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

Java线程与并发编程实践----锁框架

Java.util.concurrent.locks包提供了一个包含多种接口和类的框架,它 针对条件进行加锁和等待.不同于对象的内置加锁同步以及java.lang.Object的等 待/通知机制,包含锁框架的并发工具类通过轮询锁.显示等待及其它方式改善这种 机制. 锁框架包含了经常使用的锁.重入锁.条件.读写锁以及冲入读写锁等类别. 一.锁(Lock) Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作.此实 现允许更灵活的结构,可以具有差别很大的属性,可以

锁-GIL-同步异步-event

一堆锁 死锁 对同一把互斥锁多次执行acquire 将导致死锁------>资源被占用一直得不到释放,导致其他资源进入阻塞状况 产生死锁的情况: ? 1:对同一把互斥锁,枷锁了多次 ? 2:一个共享资源要访问必须具备多把锁,但是这些锁被不同线程或进程持有,就会导致相互等待对方释放资源,从而程序卡死 解决情况: ? 1:抢锁,按照相同循序去抢 ? 2:给抢锁,加上超时,若超时则放弃执行 给acquire加上超时,可以保证线程不会卡死 l=Lock() l.acquire() l.acquire(t

【java7并发编程实战】—–线程同步基础:synchronized

在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者.写相同的数据,访问相同的文件等等.对于这种情况如果我们不加以控制,是非常容易导致错误的.在java中,为了解决这个问题,引入临界区概念.所谓临界区是指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问. 在java中为了实现临界区提供了同步机制.当一个线程试图访问一个临界区时,他将使用一种同步机制来查看是不是已经有其他线程进入临界区.如果没有则他就可以进入临界区,否则他就会被同步机制挂起,指定进入的线程离开这个临界区

并发编程艺术-锁类型以及底层原理

Java并发编程艺术-并发机制的底层原理实现 1.Volatile 定义: Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量. volatile借助Java内存模型保证所有线程能够看到最新的值.(内存可见性) 实现原理: 将带有volatile变量操作的Java代码转换成汇编代码后,可以看到多了个lock前缀指令(X86平台CPU指令).这个lock指令是关键,在多核处理器下实现两个重要操作: 1.将当前处理器缓存行的数据写回到系

【并发编程】多线程程序同步策略

目录 C++11线程使用初探 采用条件变量等待某个事件或条件发生 线程安全的队列适配器 C++11线程使用初探 std::thread #include <thread> 只读的共享数据在多个线程间不存在Race condition的危险,而可读可写共享数据在线程间共享时则需做好线程同步,即数据保护,主要包括lock-based和lock-free策略. 常见的以互斥锁保护多线程间的共享数据,保证某一时刻仅有一个线程访问共享数据,导致线程间数据保护是串行,因此在多线程环境中,锁保护的区域越小,

java并发编程常见锁类型

锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息.锁是解决并发冲突的重要工具.在开发中我们会用到很多类型的锁,每种锁都有其自身的特点和适用范围.需要深刻理解锁的理念和区别,才能正确.合理地使用锁.常用锁类型乐观锁与悲观锁悲观锁对并发冲突持悲观态度,先取锁后访问数据,能够较大程度确保数据安全性.而乐观锁认为数据冲突的概率比较低,可以尽可能多地访问数据,只有在最终提交数据进行持久化时才获取锁.悲观锁总是先获取锁,会增加很多额外的开销,

java并发编程使用锁进行数据同步操作一

项目中总是出现招标项目超投的情况,最开始总是觉得应该使用框架Hibernate自带的并发策略中的乐观锁(version)解决问题,参考了很多网上的资料,也参考了Hibernate的帮助文档,由于对Hibernate乐观锁机制不了解,问题就一直没有解决. 最近在看Java并发编程相关知识,了解了些许并发,线程,锁的知识.想到了这个问题,曾经使用Synchroized关键字时总是苦于无法获取同一个对象,导致解决方案无效.这次采用的方案是:创建了静态的HashMap<Integer,Lock>,初始