zoukankan      html  css  js  c++  java
  • (三十三)线程

    一、线程

    是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。

    二、线程的两种创建方式

    1.第一种方式

    from  threading  import  Thread
      T = Thread(function,args=(arg1,arg2,...))
    T.start()
    

      

    2.第二种方式

    from  threading  import  Thread
      Class MThread(Thread):
          Def run(self):
    Pass
    mt = MThread()
    mt.start()
    

      

    三、线程的空间

    1. 查看pid
    import os
    def f():
        print(os.getpid())
    t = Thread(target=f,)
    t.start()
    

      

    2.线程空间不是隔离的

    import os
    def f():
        print('子线程pid:', os.getpid())#(1)
    t = Thread(target=f,)
    t.start()
    print('主线程pid:', os.getpid())#(2)
    说明:(1)、(2)两处的值是一样的。可见,线程都是在一个进程内,而一个进程都有自己独立的空间。
    

      

    3.线程与进程的效率对比

    1)只是创建线程和进程

    def f1():
        pass
    def f2():
        pass
    if __name__ == '__main__':
        t_s = time.time()
        t_lst = []
        for i in range(20):
            t = Thread(target=f1,)
            t.start()
            t_lst.append(t)
    
        for tt in t_lst:
            tt.join()
        t_e = time.time()
    
        p_s = time.time()
        p_lst = []
        for i in range(20):
            p = Process(target=f2,)
            p.start()
            p_lst.append(p)
    
        for pp in p_lst:
            pp.join()
        p_e = time.time()
    
        print('线程创建时间:', t_e - t_s)
        print('进程创建时间:', p_e - p_s)
    
    结果:
    线程创建时间: 0.004002809524536133
    进程创建时间: 1.838303565979004
    

      

    通过结果可以看出来,同样创建20个,线程只需要了0.004秒,也就是4毫秒,而创建进程却是1838.3毫秒。得出一个结论:进程创建过程比线程创建过程要麻烦。因为进程创建的过程是,需要在内存里开辟一个空间,把解释器代码加载进来,还需要把自己写的程序也加载进去。而线程是进程里的一个实体,只需要进程中的一点资源。所有线程共享进程中的所有资源。

    (2)再来一个线程中有I/O阻塞的比较

    (2)def f1():
        print('f1>>>>>aaaaa')
        time.sleep(1)
        print("f1>>>>>bbbbb")
    def f2():
        print('f2>>>>>aaaaa')
        time.sleep(1)
        print("f2>>>>>bbbbb")
    if __name__ == '__main__':
        t_s = time.time()
        t_lst = []
        for i in range(20):
            t = Thread(target=f1,)
            t.start()
            t_lst.append(t)
    
        for tt in t_lst:
            tt.join()
        t_e = time.time()
    
        p_s = time.time()
        p_lst = []
        for i in range(20):
            p = Process(target=f2,)
            p.start()
            p_lst.append(p)
    
        for pp in p_lst:
            pp.join()
        p_e = time.time()
    
        print('线程创建时间:', t_e - t_s)
        print('进程创建时间:', p_e - p_s)
    结果:
    。。。。。
    线程创建时间: 1.0074326992034912
    进程创建时间: 3.006932497024536
    

      

    线程只是在一个进程中操作,这样就利用多道技术,实现了并发(Python中的线程不能实现多核方式,后面介绍)。进程却要创建20个,开销,时间都要比线程的多。

    (3)这个是计算型的操作

    def f1():
        # print('f1>>>>>aaaaa')
        # time.sleep(1)
        # print("f1>>>>>bbbbb")
        n = 10
        for i in range(10000000):
            n += i
    def f2():
        # print('f2>>>>>aaaaa')
        # time.sleep(1)
        # print("f2>>>>>bbbbb")
        n = 10
        for i in range(10000000):
            n += i
    if __name__ == '__main__':
        t_s = time.time()
        t_lst = []
        for i in range(5):
            t = Thread(target=f1,)
            t.start()
            t_lst.append(t)
    
        for tt in t_lst:
            tt.join()
        t_e = time.time()
    
        p_s = time.time()
        p_lst = []
        for i in range(5):
            p = Process(target=f2,)
            p.start()
            p_lst.append(p)
    
        for pp in p_lst:
            pp.join()
        p_e = time.time()
    
        print('线程操作时间:', t_e - t_s)
        print('进程操作时间:', p_e - p_s)
    结果:
    线程操作时间: 3.7418324947357178
    进程操作时间: 2.925071954727173
    

      

    好神奇啊,这次进程使用的时间短了,线程的却多了。这也是因为,线程不能使用多核技术。

    四、锁

    1. Lock

       先看第一段代码:

    num = 100
    def f():
        global num
        tmp = num
        tmp -= 1
        time.sleep(0.01)
        num = tmp
    
    if __name__ == '__main__':
        t_lst = []
        for i in range(10):
            t1 = Thread(target=f)
            t1.start()
            t_lst.append(t1)
        [t.join() for t in t_lst]
        print(num)
    
    执行结果:
    99
    

      

    上面这段代码的作用是,想循环10次,为num每次减1,但是结果却是99。原因就是,线程执行的太快了,导致10个线程都执行到了time.sleep0.01)这里,然后都去等着操作系统再次调用。调用到了后,再次执行赋值的操作,这样num就是99了。

    为了数据安全,加把锁吧,看代码:

    num = 100
    def f(loc):
        global num
        loc.acquire()
        tmp = num
        tmp -= 1
        time.sleep(0.01)
        num = tmp
        loc.release()
    
    if __name__ == '__main__':
        loc = Lock()
        t_lst = []
        for i in range(10):
            t1 = Thread(target=f,args=(loc,))
            t1.start()
            t_lst.append(t1)
        [t.join() for t in t_lst]
        print(num)
    结果:
    90
    

      

    这次完成了心愿,结果是90了。当第一个线程拿到锁后,执行所有的操作,即便有sleep需要等待,其他9个线程也得等着,必须等着第一完成了。第一个完成后,接下来就是第二个线程,也跟第一个一样,不管其他的怎么着急,就是慢慢执行自己的。依次类推,最后结果就是90了。

    2.死锁

    在工作中有可能会有锁的嵌套,稍有不慎,那么就会死锁了。还是看代码:

    # def f1(locA, locB):
    #     locA.acquire()
    #     print('f1aaaaaaaaaaaa')
    #     time.sleep(0.1)
    #     locB.acquire()
    #     print('f1bbbbbbbbb')
    #     locB.release()
    #     locA.release()
    # def f2(locA, locB):
    #     locB.acquire()
    #     print('f2--aaaaaaaa')
    #     time.sleep(0.1)
    #     locA.acquire()
    #     print('f2--bbbbbbbb')
    #     locA.release()
    #     locB.release()
    # if __name__ == '__main__':
    #     locA = Lock()
    #     locB = Lock()
    #
    #     t1 = Thread(target=f1, args=(locA, locB))
    #     t2 = Thread(target=f2, args=(locA, locB))
    #
    #     t1.start()
    #     t2.start()
    

      

    运行后,会看到程序一直处于运行中。不会往下走了,因为线程t1等着要locB的锁,而线程t2等着要线程locA的锁,从而导致两边就这样互相等待着,程序一直不运行。

    3.递归锁

    为了解决死锁,Python出现了递归锁。看代码:

    # def f1(locA, locB):
    #     locA.acquire()
    #     print('f1aaaaaaaaaaaa')
    #     time.sleep(0.1)
    #     locB.acquire()
    #     print('f1bbbbbbbbb')
    #     locB.release()
    #     locA.release()
    # def f2(locA, locB):
    #     # locB.acquire()
    #     with locB:
    #         print('f2--aaaaaaaa')
    #         time.sleep(0.1)
    #         # locA.acquire()
    #         with locA:
    #             print('f2--bbbbbbbb')
    #         # locA.release()
    #     # locB.release()
    # if __name__ == '__main__':
    #     locA = locB = RLock()
    #     t1 = Thread(target=f1, args=(locA, locB))
    #     t2 = Thread(target=f2, args=(locA, locB))
    #
    #     t1.start()
    #     t2.start()
    

      

    递归锁,当acquire时,内部会有计数器,加1;前面acquire几次,就会记为几,当释放时,会依次再把锁释放掉。

    4.GIL

    这个是加载cpython解释器上的一把锁,因为它而导致Python的线程不能使用多核技术,只能串行。看图:

                          

    接下来看图说话:当我们运行了一个py文件后,其实就是启动了一个进程,操作系统就会把代码读取到内容中,并为这个进程分配相应的内存空间。在这个进程中,还会读入解释器的代码。编辑器会把这些代码处理成C语言的字节码,然后虚拟机把这些字节码再处理成二进制,这样CPU就可以处理了。

    GIL锁就是加在解释器上的,每次只能有一个线程拿到这个GIL锁,其他的线程只能等待前面的把锁释放了再拿。遇到I/O阻塞的,操作系统,会把GIL锁拿回来,交给下一个线程。如果再遇到I/O阻塞还会继续拿过来交给下一个线程。这样就实现了类似单核的并发。

    还有一些计算型的程序,使用线程时,第一个线程拿到了GIL锁后,会一直执行完毕。然后,操作系统把锁再交给下一个线程,这个线程还是从头执行到尾。也就是遇到计算型,中间没有I/O阻塞的程序,就是串行,一个一个线程去执行。

    这里,就可以看出前面进程和线程比较时,关于时间多少的问题了。

  • 相关阅读:
    POJ 2289 Jamie's Contact Groups / UVA 1345 Jamie's Contact Groups / ZOJ 2399 Jamie's Contact Groups / HDU 1699 Jamie's Contact Groups / SCU 1996 Jamie's Contact Groups (二分,二分图匹配)
    HDU 2389 Rain on your Parade / HUST 1164 4 Rain on your Parade(二分图的最大匹配)
    CJOJ 1494 【网络流24题】 搭配飞行员(二分图最大匹配)
    Luogu 1402 酒店之王(二分图最大匹配)
    CJOJ 1943 【重庆八中模拟赛】寻找代表元(二分图最大匹配)
    Luogu 2756 飞行员配对方案问题(二分图最大匹配)
    Luogu 1559 运动员最佳匹配问题(带权二分图最大匹配)
    Luogu 1894 [USACO4.2]完美的牛栏The Perfect Stall / POJ 1274 The Perfect Stall(二分图最大匹配)
    Conda和Python的国内安装源
    快速安装Rainbond——开源企业级Paas平台
  • 原文地址:https://www.cnblogs.com/asia-yang/p/10356207.html
Copyright © 2011-2022 走看看