• python 线程


    一、线程背景 (详情参考:https://www.cnblogs.com/clschao/articles/9684694.html)

      1、进程

      之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

      进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。

      2、为什么要有线程

       进程的不足:

    (1)进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

    (2)进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

      3、注意:(1)进程是资源分配的最小单位,线程是CPU调度的最小单位.

          (2)每一个进程中至少有一个线程。 
          (3)进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是                                    cpu上的执行单位。
     
    二、进程与线程的关系
     
      线程与进程的区别可以归纳为以下4点:
      1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
      2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。(就类似进程中的锁的作用)
      3)调度和切换:线程上下文切换比进程上下文切换要快得多。
      4)在多线程操作系统中(现在咱们用的系统基本都是多线程的操作系统),进程不是一个可执行的实体,真正去执行程序的不是进程,是线程,你可以理解进程就是一个线程的容器。
     

      在这里我们简单总结一下:

        进程是最小的内存分配单位

        线程是操作系统调度的最小党委

        线程被CPU执行了

        进程内至少含有一个线程

        进程中可以开启多个线程 

          开启一个线程所需要的时间要远小于开启一个进程

          多个线程内部有自己的数据栈,数据不共享

          全局变量在多个线程之间是共享的

      
     
    三、Threading 模块
      multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍(官方链接
      1、线程的创建的两种方法
    from threading import Thread
    
    def fun(n):
        print(n)
    
    if __name__ == '__main__':
        t=Thread(target=fun,args=(123,))
        t.start()
    
        print("主线程结束")
    方式一:Thread (target=...)
    class MyThread(Thread):
        def __init__(self,n):
            super(MyThread, self).__init__()
            self.n=n
        def run(self):
            print(self.n)
            print("xxxxxxxxxxxxx")
    
    if __name__ == '__main__':
        t=MyThread(456)
        t.start()
        print("父线程jieshu")
    方式二:class MyThread()

      

      2、线程 t.join() :等待子线程执行完后才会继续执行主线程的代码

    import time
    from threading import Thread
    
    def func():
        time.sleep(1)
        print('我是子线程')
    
    if __name__ == '__main__':
    
        t = Thread(target=func,)
        t.start()
    
        print('开始等待子线程了')
        t.join()  #阻塞,等待子线程执行完毕,才继续执行
        print('主线程结束')
    线程join

      

      3、线程的其他方法

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
    import time
    from threading import Thread
    from threading import current_thread
    import threading
    
    
    def func():
        time.sleep(3)
    
        # current_thread().ident
        print('我是子线程,名字是',current_thread().getName())
        print('我是子线程,id是',current_thread().ident)
    
    if __name__ == '__main__':
    
        for i in range(10):
            t = Thread(target=func,)
            t.start()
    
            # print(t.isAlive())
        print(threading.enumerate())
        print(threading.activeCount())
    
        print('主线程结束')
    其他方法使用实例

      

      4、进程与线程效率对比

    import time
    from threading import Thread
    from multiprocessing import Process
    
    def func():
        # time.sleep(3)
        print('xxxx')
    
    if __name__ == '__main__':
    
        t_list = []
        t_s_t = time.time() #线程执行起始时间
        for i in range(100):
            t = Thread(target=func,)
            t_list.append(t)
            t.start()
        [tt.join() for tt in t_list]
        t_e_t = time.time() #线程执行结束时间
        t_dif_t = t_e_t - t_s_t #线程执行所需时间
    
        p_list = []
        p_s_t = time.time() #进程执行起始时间
        for i in range(100):
            p = Process(target=func,)
            p_list.append(p)
            p.start()
        [pp.join() for pp in p_list]
        p_e_t = time.time() #进程执行结束时间
        p_dif_t = p_e_t - p_s_t #进程执行所需时间
        print('多线程的时间>>>',t_dif_t)
        print('多进程的时间>>>',p_dif_t)
    
        print('主线程结束')
    多进程与多线程效率对比

      

      5、数据共享信息安全问题验证

      结果:不可靠。会造成数据混乱。   

    import time
    from threading import Thread
    
    num = 100
    
    def func():
        # time.sleep(0.01)
        global num
        num -= 1
    
    if __name__ == '__main__':
    
        # for i in range()
    
        t = Thread(target=func,)
        t.start()
        t.join()
        print('主线程的num',num)
    修改全局变量的方式验证
    import time
    from threading import Thread
    
    num = 100
    def func():
        # time.sleep(3)
        global num
        tep = num
        time.sleep(0.001)
        tep = tep - 1
        num = tep
        # num -= 1
    
    if __name__ == '__main__':
        t_list = []
        for i in range(100):
            t = Thread(target=func,)
            t_list.append(t)
            t.start()
        [tt.join() for tt in t_list]
        # t.join()
        print('主线程的num',num)
    加长版验证
      6、验证子线程和主线程在同一个进程(os.getpid())
     
    import time
    from threading import Thread
    import os
    
    def func():
        time.sleep(1)
    
        print('我是子线程,我的pid是',os.getpid())
    
    if __name__ == '__main__':
    
        t = Thread(target=func,)
        t.start()
    
        print('开始等待子线程了')
        print('主线程的pid',os.getpid())
        t.join()
        print('主线程结束')
    同属一个进程验证

      

      7、加锁解决数据不安全问题 (牺牲了效率,保证了数据安全)

    import time
    from threading import Thread,Lock
    
    num = 100
    def func(tl):
        # time.sleep(3)
        print('xxxxx')
        time.sleep(1)
        global num
        tl.acquire() #上锁
        tep = num
        time.sleep(0.001)
        tep = tep - 1
        num = tep
        tl.release() #释放锁
        # num -= 1
    
    if __name__ == '__main__':
        tl = Lock()
        t_list = []
        for i in range(10):
            t = Thread(target=func,args=(tl,))
            t_list.append(t)
            t.start()
        [tt.join() for tt in t_list]
        # t.join()
        print('主线程的num',num)
    上锁解决数据不安全

      

      8、死锁现象

        现象描述:双方互相等待对方释放对方手里锁,然后拿到的那个锁

        所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

    import time
    from threading import Thread,Lock,RLock
    
    def func1(lock_A,lock_B):
        lock_A.acquire()
        time.sleep(0.5)
        print('alex拿到了A锁')
        lock_B.acquire()
        print('alex拿到了B锁')
        lock_B.release()
        lock_A.release()
    
    def func2(lock_A,lock_B):
        lock_B.acquire()
        print('taibai拿到了B锁')
        lock_A.acquire()
        print('taibai 拿到了A锁')
        lock_A.release()
        lock_B.release()
    
    if __name__ == '__main__':
        lock_A = Lock()
        lock_B = Lock()
        t1 = Thread(target=func1,args=(lock_A,lock_B))
        t2 = Thread(target=func2,args=(lock_A,lock_B))
        t1.start()
        t2.start()
    死锁现象

       

         死锁现象: 引入递归锁RLock(lock_A=lock_B=RLock())

        递归锁RLock自带计数器,遇到acquire计数器+1;遇到release,计数器-1,递归锁被一个对象夺走后就不能被其他对象获得,只有等释放为0时候才会开锁,让其他的进程抢夺。

    import time
    from threading import Thread,Lock,RLock
    
    def func1(lock_A,lock_B):
        lock_A.acquire()
        time.sleep(0.5)
        print('alex拿到了B锁')
        lock_B.release()
        lock_A.release()
    
    def func2(lock_A,lock_B):
        lock_B.acquire()
        print('taibai拿到了B锁')
        lock_A.acquire()
        print('taibai 拿到了A锁')
        lock_A.acquire()
        print('taibai 拿到了A锁')
        lock_A.release()
        lock_B.release()
    
    if __name__ == '__main__':
    
        lock_A = lock_B = RLock()  #递归深度不限,只有当递归锁计数器为0,才会释放给其他线程用
        print(id(lock_A)) #具有相同内存地址
        print(id(lock_B)) #具有相同的内存地址
    
        t1 = Thread(target=func1,args=(lock_A,lock_B))
        t2 = Thread(target=func2,args=(lock_A,lock_B))
        t1.start()
        t2.start()
    递归解决死锁问题

       典型问题:科学家吃面,只有一碗面,一把叉子

    import time
    from threading import Thread,Lock
    noodle_lock = Lock()
    fork_lock = Lock()
    def eat1(name):
        noodle_lock.acquire()
        print('%s 抢到了面条'%name)
        fork_lock.acquire()
        print('%s 抢到了叉子'%name)
        print('%s 吃面'%name)
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print('%s 抢到了叉子' % name)
        time.sleep(1)
        noodle_lock.acquire()
        print('%s 抢到了面条' % name)
        print('%s 吃面' % name)
        noodle_lock.release()
        fork_lock.release()
    
    for name in ['taibai','egon','wulaoban']:
        t1 = Thread(target=eat1,args=(name,))
        t2 = Thread(target=eat2,args=(name,))
        t1.start()
        t2.start()
    面条与叉子

       9、守护线程(守护进程必须在start之前)

      无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕,如果主xx时间够长,守护xx执行完毕后被销毁,如果主xx时间较短的话,守护xx会终并销毁,如果主。需要强调的是:运行完毕并非终止运行。

    #1.对主进程来说,运行完毕指的是主进程代码运行完毕
    #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
    import time
    from threading import Thread
    from multiprocessing import Process
    
    def func1():
        time.sleep(3)
        print('任务1结束')
    
    def func2():
        time.sleep(2)
        print('任务2结束')
    
    if __name__ == '__main__':
    
         t1 = Thread(target=func1,)
         t2 = Thread(target=func2,)
         # t1.daemon = True
        # t2.setDaemon(True)  #将t1设置为守护线程
         t1.setDaemon(True)  #将t1设置为守护线程
         t1.start()
         t2.start()
         print('主线程结束')
    
    #结果显示
    主线程结束
    任务2
    
    #结果分析:t1被设置为守护进程,他线程时间为3s,t2线程的时间是2s,
    当t2线程执行完后,结束守护进程,主线程代码就会执行到print(”主线程“),
    守护进程实例与解说
    # from threading import Thread
    # import time
    #
    #
    # def sayhi(name):
    #     time.sleep(2)
    #     print('%s say hello' % name)
    #
    #
    # if __name__ == '__main__':
    #     t = Thread(target=sayhi, args=('taibai',))
    #     t.setDaemon(True)  # 必须在t.start()之前设置
    #     t.start()
    #
    #     print('主线程')
    #     print(t.is_alive())
    #     '''
    #     主线程
    #     True
    #     '''
    
    from threading import Thread
    from multiprocessing import Process
    import time
    
    
    def func1():
        while True:
            print(666)
            time.sleep(0.5)
    
    
    def func2():
        print('hello')
        time.sleep(3)
    
    
    if __name__ == '__main__':
        # t = Thread(target=func1,)
        # t.daemon = True  #主线程结束,守护线程随之结束
        # # t.setDaemon(True) #两种方式,和上面设置守护线程是一样的
        # t.start()
        # t2 = Thread(target=func2,) #这个子线程要执行3秒,主线程的代码虽然执行完了,但是一直等着子线程的任务执行完毕,主线程才算完毕,因为通过结果你会发现我主线程虽然代码执行完毕了,
        # 但是主线程的的守护线程t1还在执行,说明什么,说明我的主线程还没有完毕,只不过是代码执行完了,一直等着子线程t2执行完毕,我主线程的守护线程才停止,说明子线程执行完毕之后,我的主线程才执行完毕
        # t2.start()
        # print('主线程代码执行完啦!')
        p = Process(target=func1, )
        p.daemon = True
        p.start()
    
        p2 = Process(target=func2, )
        p2.start()
        time.sleep(1)  # 让主进程等1秒,为了能看到func1的打印效果
        print('主进程代码执行完啦!')  # 通过结果你会发现,如果主进程的代码运行完毕了,那么主进程就结束了,因为主进程的守护进程p随着主进程的代码结束而结束了,守护进程被回收了,这和线程是不一样的,主线程的代码完了并不代表主线程运行完毕了,需要等着所有其他的非守护的子线程执行完毕才算完毕
    守护线程2实例

    四、信号量

      

      同进程的一样

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

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

    import time
    import random
    from threading import Thread,Semaphore
    
    def func1(i,s):
        s.acquire()
        # time.sleep(1)
        print('客官%s里边请~~'%i)
        time.sleep(random.randint(1, 3))
        s.release()
    
    
    if __name__ == '__main__':
        s = Semaphore(4)
        for i in range(10):
            t = Thread(target=func1,args=(i,s))
            t.start()
    Semaphore实例

    五、事件(Event)

      事件的基本方法:

    event.isSet():返回event的状态值;
    event.wait():如果 event.isSet()==False将阻塞线程;
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    event.clear():恢复event的状态值为False。
  • 相关阅读:
    BZOJ 3033 太鼓达人(DFS+欧拉回路)
    HDU 5121 Just A Mistake
    HDU 5120 Intersection
    HDU 5119 Happy Matt Friends
    HDU 5117 Fluorescent
    BZOJ 1088: [SCOI2005]扫雷Mine
    Codeforces 994 C
    BZOJ 2242: [SDOI2011]计算器
    HDU 4609 3-idiots
    算法笔记--FFT && NTT
  • 原文地址:https://www.cnblogs.com/angle6-liu/p/10045634.html
Copyright © 2020-2023  润新知