zoukankan      html  css  js  c++  java
  • 并发编程(三) 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.
    

    ps:python解释器有很多种 最常见的就是Cpython解释器
    GIL的存在是因为CPython解释器的内存管理不是线程安全的

    垃圾回收机制
    1.引用计数
    2.标记清除
    3.分代回收

    引用计数:值和变量绑定关系的个数
    标记清除:内存快满的时候,扫描内存,没有绑定变量的值会被清除掉
    分代回收:频繁扫描是无意义的,因此采取措施降低扫描频率。垃圾回收是会消耗资源的,而程序运行内部会用到变量与值,并且部分是于常量,要减少垃圾回收消耗的时间
    
    GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全 
    用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
    

    python的多线程没法利用多核优势 是不是就是没有用了?

    研究python的多线程是否有用需要分情况讨论

    四个任务 计算密集型的

    分析:计算密集,无阻塞态,一个线程走完另一个线程启动,因此开4个线程需要40秒
    开4个进程,那就是并行,因此只需要10秒

    单核情况下
        开线程更省资源
    多核情况下
        开进程 10s
        开线程 40s
    
    计算密集型,多核开进程最快
    from multiprocessing import Process
    from threading import Thread
    import os, time
    
    
    def work():
        res = 0
        for i in range(10000000):
            res *= i
    
    
    if __name__ == '__main__':
        l = []
        # print(os.cpu_count())  # 本机为6核
        start = time.time()
        for i in range(6):
            p=Process(target=work)     # 耗时 2.8391623497009277
            # p = Thread(target=work)  # 耗时 4.586262226104736
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print('run time is %s' % (stop - start))
    
    
    四个任务 IO密集型的

    分析:阻塞态,开哪个都可以,都是等待,开进程开销还更大,因此开线程

    单核情况下
        开线程更节省资源
    多核情况下
        开线程更节省资源
    
    # IO密集型开线程更节省资源
    from multiprocessing import Process
    from threading import Thread
    import threading
    import os,time
    def work():
        time.sleep(2)
    
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count()) #本机为6核
        start=time.time()
        for i in range(4000):
            p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
            # p=Thread(target=work) #耗时2.051966667175293s多
            l.append(p)
            p.start()
        for p in l:
            p.join()#join的作用:让列表中的线程不要接着往下走!
        stop=time.time()
        print('run time is %s' %(stop-start))
    

    join的作用:让进程等待结束再进行下一个进程(后来总结:让列表中的线程不要接着往下走!)

    如何实现TCP服务端实现并发?

    思路:while循环开线程

    #服务端 
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    def talk(conn):
        while True:
            try:
                data = conn.recv(1024)
                if len(data) == 0:break
                print(data.decode('utf-8'))
                conn.send(data.upper())
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()
    
    while True:
        conn, addr = server.accept()  # 监听 等待客户端的连接  阻塞态
      
        t = Thread(target=talk,args=(conn,))
        t.start()
    
    ps:常见问题
    GIL是不是python解释器的特点?
    GIL是Cpython解释器的特点,python解释器有多个语言编写的版本
    

    GIL与普通互斥锁

    from threading import Thread
    import time
    
    n = 100
    
    
    def task():
        global n
        tmp = n
        # time.sleep(1)
        n = tmp -1
    
    t_list = []
    for i in range(100):
        t = Thread(target=task)
        t.start()
        t_list.append(t)
    
    for t in t_list:
        t.join()#
    
    print(n)
    

    之前不明白第二个for循环join什么时候开始运行,在线程sleep的时候,原本是直接到最下面执行print,现在多了两行代码,走到join这里,发现要等待,不能跳着运行

    死锁

    死锁的原因是一个线程抢两把锁

    from threading import Thread,Lock,current_thread,RLock
    import time
    
    mutexA = Lock()
    mutexB = Lock()
    
    class MyThread(Thread):
        def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
            self.func1()
            self.func2()
    
        def func1(self):
            #抢锁外,隔离
            mutexA.acquire()
            print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
            mutexB.acquire()
            print('%s抢到了B锁'%self.name)
            mutexB.release()
            print('%s释放了B锁'%self.name)
            mutexA.release()
            print('%s释放了A锁'%self.name)
    
        def func2(self):
            mutexB.acquire()
            print('%s抢到了B锁'%self.name)
            # time.sleep(1)
            mutexA.acquire()
            print('%s抢到了A锁' % self.name)
            mutexA.release()
            print('%s释放了A锁' % self.name)
            mutexB.release()
            print('%s释放了B锁' % self.name)
    
    
    for i in range(10):
        t = MyThread()
        t.start()
    
    

    现在是线程1跑到func2,抢到了B锁,准备抢A锁,而A锁在线程2的手上产生了死锁

    创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
    self.name等价于current_thread().name。线程号的意思

    不要轻易动“锁”有关的事!

    解决方法:Rlock递归锁

    所有取名递归,就是它可以有一个计数的功能,数值可加可减

    将锁替换为Rlock()即可
    from threading import Thread,Lock,current_thread,RLock
    import time
    from threading import Thread,Lock,current_thread,RLock
    import time
    """
    Rlock可以被第一个抢到锁的人连续的acquire和release
    每acquire一次锁身上的计数加1
    每release一次锁身上的计数减1
    只要锁的计数不为0 其他人都不能抢
    
    """
    # mutexA = Lock()
    # mutexB = Lock()
    mutexA = mutexB = RLock()  # A B现在是同一把锁
    
    
    class MyThread(Thread):
        def run(self):  # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
            mutexB.acquire()
            print('%s抢到了B锁'%self.name)
            mutexB.release()
            print('%s释放了B锁'%self.name)
            mutexA.release()
            print('%s释放了A锁'%self.name)
    
        def func2(self):
            mutexB.acquire()
            print('%s抢到了B锁'%self.name)
            time.sleep(1)
            mutexA.acquire()
            print('%s抢到了A锁' % self.name)
            mutexA.release()
            print('%s释放了A锁' % self.name)
            mutexB.release()
            print('%s释放了B锁' % self.name)
    
    for i in range(10):
        t = MyThread()
        t.start()
    
    
    

    信号量

    与互斥锁类似

    互斥锁,就是多个线程抢一个坑位

    信号量,就是多个线程抢多个坑位

    互斥锁:一个厕所(一个坑位)
    信号量:公共厕所(多个坑)

    导入模块from threading import Semaphore,Thread
    from threading import Semaphore,Thread
    import time
    import random
    
    sm = Semaphore(5)  # 造了一个含有五个的坑位的公共厕所
    #以下位置进行加锁
    def task(name):
        sm.acquire()#加锁位置
        print('%s占了一个坑位'%name)
        time.sleep(random.randint(1,3))
        sm.release()#放锁位置
        print('%s走了'%name)
    for i in range(40):
        t = Thread(target = task ,args =(i,))
        t.start()
    
    结果:
    0占了一个坑位
    1占了一个坑位
    2占了一个坑位
    3占了一个坑位
    4占了一个坑位
    4走了
    0走了
    1走了
    3走了
    2走了
    

    event事件

    之前的join方法是让主进程等待子进程

    案例演示:等红绿灯

    操作步骤:

    1先生成一个event对象

    2发信号e.set()

    3.等待信号e.wait()

    from threading import Event,Thread
    import time
    
    # 先生成一个event对象
    e = Event()
    
    def light():
        print('红灯正亮着')
        time.sleep(1)
        e.set()  # 发信号
        print('绿灯亮了')
    
    def car(name):
        print('%s正在等红灯'%name)
        e.wait()  # 等待信号
        print('%s加油门飙车了'%name)
    
    t = Thread(target=light)
    t.start()
    
    for i in range(10):
        t = Thread(target=car,args=('伞兵%s'%i,))
        t.start()
    
    

    线程q

    同一个进程下的多个线程本来就是数据共享 为什么还要用队列

    因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题

    因为锁操作的不好极容易产生死锁现象

    
    q = queue.Queue()
    q.put('hahha')
    print(q.get())
    
    q = queue.LifoQueue()
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    
    q = queue.PriorityQueue()
    # 数字越小 优先级越高
    q.put((10,'haha'))
    q.put((100,'hehehe'))
    q.put((0,'xxxx'))
    q.put((-10,'yyyy'))
    print(q.get())
    
    
  • 相关阅读:
    音频、摄像机操作
    调用系统相机及摄像机
    图片的放大缩小
    haxm intelx86加速模拟器的安装
    mac eclipse 下安装subclipse
    文件多线程下载实现
    windows与linux之间传输文件
    ZeroMQ接口函数之 :zmq_setsockopt –设置ZMQ socket的属性
    使用C语言在windows下一口气打开一批网页
    Net-SNMP是线程安全的吗
  • 原文地址:https://www.cnblogs.com/ZDQ1/p/11353103.html
Copyright © 2011-2022 走看看