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())
    
    
  • 相关阅读:
    42. Trapping Rain Water
    223. Rectangle Area
    645. Set Mismatch
    541. Reverse String II
    675. Cut Off Trees for Golf Event
    安装 VsCode 插件安装以及配置
    向上取整 向下取整 四舍五入 产生100以内随机数
    JS 判断是否为数字 数字型特殊值
    移动端初始配置,兼容不同浏览器的渲染内核
    Flex移动布局中单行和双行布局的区别以及使用
  • 原文地址:https://www.cnblogs.com/ZDQ1/p/11353103.html
Copyright © 2011-2022 走看看