并发编程 07.06

1.一堆锁

死锁现象:某个资源被占用一直得不到释放导致其它需要这个资源的线程进入阻塞状态

产生死锁的情况

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

解决方案

  1. 抢锁一定按照相同的顺序去抢
  2. 给锁加上超时,如果超时则放弃执行

2.递归锁

递归锁与普通锁的区别

多线程之间都有互斥的效果

不同在于 递归锁 同一个线程可以对这个锁执行多次acquire

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

3.信号量

作用:可以限制同时并发执行公共代码的线程数量,若限制数量为1,则相当于普通锁

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

from threading import Thread,Semaphore,current_thread
import time

s = Semaphore(5)

def task():
    s.acquire()
    time.sleep(1)
    print(current_thread().name)
    s.release()

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

4.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  中  这个全局解释器锁 或者成为GIL  是一个互斥锁  ,是为了阻止做个本地线程在同一时间执行python字节码
因为cpython的内存管理是非线程安全的,这个锁是非常必要的  因为其他越来越多的特性依赖这个特性   

需要这把锁的原因

线程安全问题具体的表现

cpython解释器与python程序之间的关系

Cpython解释器与GC的问题

python会自动帮我们处理垃圾,清理垃圾也是一堆代码 也需要开启一个线程来执行 也就是说就算程序没有自己开启线程 内部也会有多个线程

GC线程与我们程序中的线程就会产生安全问题

带来的问题

GIL是一把互斥锁,互斥锁将导致效率降低

具体表现是在CPython 即便开启了多线程 而且CPU也是多核的 却无法并行执行任务 因为解释器只有一个 同一时间只能有一个任务在执行

解决措施

现阶段暂时莫办法解决,只能尽可能的避免GIL锁影响我们的效率

  1. 使用多进程能够实现并行,从而更好地利用多核CPU
  2. 对任务进行分类
    1. 计算密集型:基本没有IO 大部分时间都在计算
    2. IO密集型 :计算任务非常少,大部分时间都在等待IO操作

关于性能的讨论

加锁的原因是为了解决现存安全问题

优于有了锁 导致Cpython中多线程不能并行只能并发,但是不可否认python

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

性能测试

# 计算密集型任务
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 = Thread(target=task)
        # p = Process(target=task)
        p.start()
        ps.append(p)

    for i in ps: i.join()
    print('共耗时',time.time()-start_time)
    
#多进程胜

#IO密集型任务
def task():
    for i in range(100):
        with open(r"1.死锁现象.py",encoding="utf-8") as f:
            f.read()

if __name__ == '__main__':
    start_time = time.time()

    ps = []
    for i in range(10):
        p = Process(target=task)
        # p = Thread(target=task)
        p.start()
        ps.append(p)

    for i in ps:i.join()
    print("共耗时:",time.time()-start_time)

# 多线程胜

GIL与自定义锁的区别

GIL锁住的是解释器级别的数据

自定义锁,锁的是解释器以外的共享资源

对于不属于解释器的数据资源就应该自己加锁处理

5.线程池与进程池

一种容器

优势:

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

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

import  time
from  concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import activeCount, enumerate, currentThread

pool = ThreadPoolExecutor(20)  # 创建一个线程池   指定最多可以容纳两个线程

def task():
    print(currentThread().name)

# 提交任务到池子中
pool.submit(task)
pool.submit(task)

time.sleep(2)
print(enumerate())
pool.shutdown() # 关闭这个线程池   阻塞直到所有任务执行完毕
print('over')
pool.submit(task) # 关闭之后就不能在提交新的任务了

time.sleep(2)
print(enumerate())

def task():
    time.sleep(1)
    print(os.getpid())

if __name__ == '__main__':
    # 进程池的使用 建议放到判断中否则 会开一对池
    pool = ProcessPoolExecutor(2)
    pool.submit(task)
    pool.submit(task)
    pool.submit(task)

6.同步异步

同步:指的是提交任务后必须在原地等待 直到任务结束

异步: 指的是提交任务后不需要再原地等待 可以继续往下执行代码

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

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

解决方法:

  1. 轮询 重复的隔一段时间就问一次 效率低 无法及时获取结果 不推荐
  2. 让任务的执行方主动通知(异步回调)可以及时拿到任务的结果 推荐方式
# 异步回调
from threading import  Thread
# 具体的任务
def task(callback):
    print('run')
    for i in range(100000000):
        1+1
    callback('ok')
#回调函数 参数为任务的结果
def finshed(res):
    print('任务完成',res)

print('start')
t = Thread(target=task,args=(finshed,))
t.start() #执行task时 没有导致主线程卡主 而是继续运行
print('over')
import time
from concurrent.futures import ThreadPoolExecutor

def task():
	time.sleep(2)
	print('finshed')
	return 'ok'

def callback(arg):
	print('任务执行完毕了',arg)
	print('任务结果:',arg.result())

    pool = ThreadPoolExecutor(10)
	res =pool.submit(task) # 异步提交方式
	print(res)
	print(res.result()) # result是阻塞的会直到 任务执行完毕
	res.add_done_callback(callback) # 为这个任务绑定回调函数
#使用案例
import time
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')

7.Event事件

线程间的状态同步

from threading import  Thread,Event
import time

# is_boot = False
e = Event()

def start_server():
    # global is_boot
    print("starting server......")
    time.sleep(3)
    print("server started!")
    # is_boot = True
    # 修改事件的值为True
    e.set()


def connect_server():
    e.wait() # 等待事件从False 变为true
    if e.is_set():
        print("连接服务器成功!")
    # while True:
    #     if is_boot:
    #         print("连接服务器成功!")
    #         break
    #     else:
    #         print("失败 服务器未启动!")
    #     time.sleep(0.5)


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

t1.start()
t2.start()
posted @ 2019-07-06 15:03  海森t  阅读(36)  评论(0)    收藏  举报