• 线程的知识点补充


    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解释器
    CIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全
    用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
        python的多线程没法利用多核优势  是不是就是没有用了?
    
    GIL的存在是因为CPython解释器的内存管理不是线程安全的
    垃圾回收机制
        1.引用计数
        2.标记清除
        3.分代回收
    研究python的多线程是否有用需要分情况讨论
    四个任务 计算密集型的  10s
    单核情况下
        开线程更省资源
    多核情况下
        开进程 10s
        开线程 40s
    
    四个任务 IO密集型的  
    单核情况下
        开线程更节省资源
    多核情况下
        开线程更节省资源
    """
    # 计算密集型
    # from multiprocessing import Process
    # from threading import Thread
    # import os,time
    # def work():
    #     res=0
    #     for i in range(100000000):
    #         res*=i
    #
    #
    # if __name__ == '__main__':
    #     l=[]
    #     print(os.cpu_count())  # 本机为6核
    #     start=time.time()
    #     for i in range(6):
    #         # p=Process(target=work) #耗时  4.732933044433594
    #         p=Thread(target=work) #耗时 22.83087730407715
    #         l.append(p)
    #         p.start()
    #     for p in l:
    #         p.join()
    #     stop=time.time()
    #     print('run time is %s' %(stop-start))
    计算密集型
    # 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()
        stop=time.time()
        print('run time is %s' %(stop-start))
    IO密集型

    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)
    首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
    
        然后,我们可以得出结论:保护不同的数据就应该加不同的锁。
    
     最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock
    
    过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限
    
      线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果

     死锁与递归锁

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

    from threading import Thread,Lock,current_thread,RLock
    import time
    
    # 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()

    RLock可以被第一个抢到锁的人连续的acquire和release

    每acquire一次锁身上的计数加1

    每release一次锁身上的计数减1

    只要锁的计数不为0   其他人都不能抢

    信号量

    同进程的一样

    Semaphore管理一个内置的计数器,
    每当调用acquire()时内置计数器-1;
    调用release() 时内置计数器+1;
    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5)

    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()
    
    for i in range(40):
        t = Thread(target=task,args=(i,))
        t.start()

    event事件

    同进程的一样

    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;
    
    event.wait():如果 event.isSet()==False将阻塞线程;
    
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    
    event.clear():恢复event的状态值为False。
    """
    起两个线程
    第一个线程:连接数据库
        等待一个信号 告诉我我们之间的网络是通的
    第二个线程:检测与数据库之间网络是否连通
        
    """
    import time
    import random
    from threading import Thread,Event
    
    def connect_db(e):
        count = 0
        while count < 3:
            e.wait(0.5)  #状态为False的时候  只等待0.5秒就结束
            if e.is_set() == True:
                print('连接数据库')
                break
            else:
                count += 1
                print('第%s次连接失败' % count)
        else:
            raise TimeoutError('数据库连接超时')
    
    def check_web(e):
        time.sleep(random.randint(0,3))
        e.set()
    
    e = Event()
    t1 = Thread(target=connect_db,args=(e,))
    t2 = Thread(target=check_web,args=(e,))
    t1.start()
    t2.start()

    线程队列

    class queue.Queue(maxsize=0) #先进先出

    import  queue
    
    q=queue.Queue()
    q.put('first')
    q.put('second')
    q.put('third')
    # q.put_nowait() #没有数据就报错,可以通过try来搞
    print(q.get())
    print(q.get())
    print(q.get())
    # q.get_nowait() #没有数据就报错,可以通过try来搞
    '''
    结果(先进先出):
    first
    second
    third
    '''
    先进先出示例

    class queue.LifoQueue(maisize=0) #last in first out

    import queue
    
    q=queue.LifoQueue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(后进先出):
    third
    second
    first
    '''
    后进先出

    class queue.PriorityQueue(maxsize=0) # 存储数据时可设置优先级的队列

    import queue
    
    q=queue.PriorityQueue()
    #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
    q.put((20,'a'))
    q.put((10,'b'))
    q.put((30,'c'))
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(数字越小优先级越高,优先级高的优先出队):
    (10, 'b')
    (20, 'a')
    (30, 'c')
    '''
    优先级队列
  • 相关阅读:
    今天本来还打算继续更新微信开发的,但是没办法接口调用次数已经达到上限了,唉
    夺命雷公狗---微信开发43----用户分组管理接口(删)
    夺命雷公狗---微信开发43----用户分组管理接口(改)
    夺命雷公狗---微信开发42----用户分组管理接口(查)
    夺命雷公狗---微信开发41----用户分组管理接口(增)
    夺命雷公狗---微信开发40----微信语言识别接口2(点歌系统)
    [LeetCode] Balanced Binary Tree
    [LeetCode] Minimum Depth of Binary Tree
    [LeetCode] Path Sum
    [LeetCode] Pascal's Triangle
  • 原文地址:https://www.cnblogs.com/KrisYzy/p/11352410.html
Copyright © 2020-2023  润新知