• python的并发和线程


    一、并发和并行的区别

    •  并行:同时做某些事,可以互不干扰的同一个时刻做几件事,例如高速公路的车道
    •  并发:也是同时做某些事,但是强调同一个时段做了几件事

    二、并发的解决

    • 食堂中午吃饭,人都涌向食堂,这就是并发,如果人很多,就是高并发

    1、队列、缓冲区

    • 假设只有一个窗口,陆续涌入食堂的人,排队打菜是比较好的方式
    • 排队就是人排成队列,先进先出,解决了资源使用的问题
    • 排成的队列其实就是一个缓冲地带,就是缓冲区

    2、争抢

    •  只开一个窗口,有可能没有秩序,也就是谁挤进去就给谁打饭,挤到窗口的人占据窗口,直到打到饭菜离开
    •  其他人继续争抢,会有一个人占据窗口,可以视为锁定窗口,窗口就不能为其他人服务了,这是一种锁机制;抢到资源就上锁,排他性的锁,其他人只能等候
    •  争抢也是一种高并发解决方案,但是不好,因为有可能很多人很长时间抢不到

    3、预处理

    • 如果排队长的原因,是由于每个人打菜等候的时间长,因为要吃的菜没有,需要现做,锁定这窗口,可以提前统计大多数最爱吃的菜品,先做好
    • 一种提前加载用户需要的数据的思路,预处理思想,缓冲常用

    4、并行

    • 成百上千的人同时来吃饭,一个队伍搞不定的,多开打饭窗口形参多个队列,如同多个车道一样,开窗口就得扩大食堂,得多雇人在每个窗口提供服务,造成成本上升
    • 日常可以通过购买更多服务器,或多开进程,进程实现并行处理,来解决并发问题
    •  如果线程在单个CPU上处理,就不是并行了,但是多数服务器都是多CPU的,服务的部署往往是多机的,分布式的,这都是并行处理

    5、提速

    •  提高单个窗口的打饭速度,也是解决并发的方式
    •  打饭人员提高工作技能,或为单个窗口配备更多的服务人员
    •  提高单个CPU性能,或单个服务器安装更多的CPU
    •  这是一种垂直扩展思想

    6、消息中间件

    • 地铁站的九曲回肠的走廊,缓冲人流,进去之后再多口安检进站
    • 常见的消息中间件有RabbitMQ,ActiveMQ(Apache),RocketMQ,kafka等

    二、进程和线程

    • 在实现了线程的操作系统中,线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个程序的执行实例就是进程
    • 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统的结构的基础
    • 进程和程序的关系:
      • 程序是源代码编译后的文件,而这些文件存放在磁盘上,当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据,它也是线程的容器
      • Linux进程有父进程,子进程,Windows的进程是平等关系
      • 线程,有时被称为轻量级进程,是程序执行流的最小单元
      • 一个标准的线程由线程ID,当前指令指针,寄存器集合和堆栈组成

    1、进程、线程的理解

    • 现代操作系统提出进程的概念,每个进程都认为自己独占所有的计算机硬件资源
    • 进程就是独立的王国,进程间不可以随便的共享数据
    • 线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程拥有自己独立的栈

    三、线程的状态

    •  就绪(Read):线程能够运行,但在等待被调度,可能线程刚刚创建启动,或刚刚从阻塞中恢复,或被其他线程抢占
    •  运行(Runing):线程正在运行
    •  阻塞(Blocked):线程等待外部事件发生而无法运行,如I/O操作
    •  终止(Terminated):进程完成,或退出,或被取消

    四、python的线程开发

    1、Thread类

    •  target:线程调用的对象,就是目标函数
    •  name: 为线程起个名字
    •  args :为目标函数传递实参,元组
    •  kwargs: 为目标函数关键字传参,字典

    2、线程启动

    举例1:
        import threading
    
        #最简单的线程程序
        def worker():
            print("I'm working")
            print('Fineshed')
    
        t = threading.Thread(target=worker, name='worker')  # 线程对象
        t.start() #启动线程
        
        通过threading.Thread创建一个线程对象,target是目标函数,name可以指定名称
        通过start()方法调用线程启动
        线程之所以执行函数,是因为线程中就是执行代码的,而最简单的封装就是函数,
        所有还是函数调用,函数执行完,线程也就退出了
        
    举例2:
        
        import threading
        import time
    
        #最简单的线程程序
        def worker():
            while True:   #线程不退出
                time.sleep(1)
                print("I'm working")
            print('Fineshed')
    
        t = threading.Thread(target=worker, name='worker')  # 线程对象
        t.start() #启动线程
    
    举例3:
            
        import threading
        import time
    
        #最简单的线程程序
        def worker():
            while True:
                if (count > 5):
                    raise RuntimeError(count)
                time.sleep(1)
                print("I'm working")
                count += 1
    
        t = threading.Thread(target=worker, name='worker')  # 线程对象
        t.start() #启动线程
        print('========End==========')
    
    python的线程没有优先级,没有线程组的概念,也不能被摧毁,停止,挂起,那也就没有恢复和中断了
        
        

    3、线程的传参

    举例1:
        import threading
        import time
    
        def add(x,y):
            print('{}+{}={}'.format(x,y,x+y, threading.current_thread().ident))
    
        thread1 = threading.Thread(target=add, name='add', args=(4,5))
        thread1.start()  #启动线程
        time.sleep(2)
    
        thread2 = threading.Thread(target=add, name='add', args=(5,),kwargs={'y':4})
        thread2.start()
        time.sleep(2)
    
        thread3 = threading.Thread(target=add, name='add', kwargs={'x':4, 'y':5})
        thread3.start()
        
        线程传参和函数传参没什么区别,本质上就是函数传参


    4、threading的属性和方法

    •  current_thread():返回当前线程对象
    •  main_thread() : 返回主线程对象
    •  activ_count() : 当前处于alive状态的线程个数
    •  enumerate() : 返回所有活着的线程的列表,不包括已经终止的线程和未开始的线程
    •  get_ident() : 返回当前线程的ID,非0整数
    •  activ_count,enumerate方法返回的值还包括主线程
    举例1:
        import threading
        import time
    
        def showthreadinfo():
            print("current thread= {}".format(threading.current_thread()))
            print("main thread= {}".format(threading.main_thread()))
            print("active count= {}".format(threading.active_count()))
    
        def worker():
            count = 0
            showthreadinfo()
            while True:
                if count > 5:
                    break
                time.sleep(1)
                count += 1
                print("I'm working")
            
        t = threading.Thread(target=worker, name='worker')
        showthreadinfo()
        t.start()

    5、Thread实例的属性和方法

    • name:只是一个名字,只是个标识符,名称可以重名
    • ident : 线程ID,它是非0整数,线程启动后才会有ID,否则为None,线程退出,此ID依旧可以,ID可以重复使用, ID必须唯一,但可以在线程退出后再利用
    • is_alive(): 返回线程是否活着
    举例1:
    
        import threading
        import time
    
        def worker():
            count = 0
            while True:
                if count > 5:
                    break
                time.sleep(1)
                count += 1
                print(threading.current_thread().name)
    
        t = threading.Thread(name='worker', target=worker)
        print(t.ident)
        t.start()
                

    6、start()和run()方法 

    •  使用start方法启动线程,是启动了一个新的线程,名字叫做worker运行,但是使用run方法的,并没有启动新的线程,就是在主线程中调用了一个普通的函数而已
    •  因此,启动线程使用start()方法才能启动多个线程

    五、多线程

    • 顾名思义,多个线程,一个进程中如果有多个线程,就是多线程,实现一种并发
    举例1:
        import threading
        import time
    
        def worker():
            count = 0
            while True:
                if count > 5:
                    break
                time.sleep(0.5)
                count += 1
                print('worker runing')
                print(threading.current_thread().name, threading.current_thread().ident)
    
        class MyThread(threading.Thread):
            def start(self):
                print('start--------------')
                super().start()
            
            def run(self):
                print('run-------------------')
                super().run() # 看看父类在做什么
    
        t1 = MyThread(name='worker1', target=worker)
        t2 = MyThread(name='worker2', target=worker)
    
        t1.start()
        t2.start()
        
        start方法启动可以看到worker1和worker2交替执行
        run方法没有开新的进程,这就是普通函数的调用,所以执行完t1.run(),然后执行t2,这就不是多线程
        
        当使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程
        
        一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程,一个进程至少有一个主线程
        其他线程称为工作线程
        


    1、线程安全

    举例1:
    
        import threading
        def worker():
            for x in range(100):
                print("{} is runing".format(threading.current_thread().name))
    
        for x in range(1,5):
            name = "worker{}".format(x)
            t = threading.Thread(name=name, target=worker)
            t.start()
    
    上例中,本以为print应该是打印文本之后紧跟着一个换行的,但是有时候确实好几个文本在一起
    后面跟上换行,而且发生这种情况的时机不确定,所以,print函数不是线程安全函数
    如果是这样,多线程编程的时候,print输出日志,不能保证一个输出一定后面立即换行了
    
    解决print函数换行
    
        1、不让print打印换行
        
    import threading
    def worker():
        for x in range(100):
            print("{} is runing.
    ".format(threading.current_thread().name),end='')
    
    for x in range(1,5):
        name = "worker{}".format(x)
        t = threading.Thread(name=name, target=worker)
        t.start()
    
        字符串是不可变的类型,它可以作为一个整体不可分割输出,end=''就不在print输出换行了
        
        2、使用logging
        标准库里面的logging模块,日志处理模块,线程安全的,生产环境代码都使用logging
        
        
    import threading
    import logging
    
    def worker():
        for x in range(100):
            print("{} is runing.
    ".format(threading.current_thread().name),end='')
            logging.warning("{} is running".format(threading.current_thread().name))
    
    for x in range(1,5):
        name = "worker{}".format(x)
        t = threading.Thread(name=name, target=worker)
        t.start()


    2、daemon线程和non-daemon线程

    • 进程靠线程执行代码,至少有一个主线程,其他线程是工作线程
    • 主线程是第一个启动的线程
    • 父线程:如果线程A中启动了一个线程B,A就是B的父线程
    • 子线程:B就是A的子线程

      python中,构造线程的时候,可以设置daemon属性,这个属性必须在start方法前设置好

    • daemon属性:表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常
    • isDaemon(): 是否是daemon线程
    • setDaemon : 设置为daemon线程,必须在start方法之前设置
    举例1:
        
        import time
        import threading
    
        def foo():
            time.sleep(5)
            for i in range(20):
                print(i)
                
        #主线程是non-daemon线程
        t = threading.Thread(target=foo, daemon=False)
        t.start()
        print('Main Thread Exiting')
    
        
        发现线程t依然执行,主线程已经执行完,但是一直等着线程t
        修改为 t= threading.Thread(target=foo, daemon=True),主线程执行完就结束了,根本没有等线程t
    
    总结:
        线程具有一个daemon属性,可以显示设置为True或Fale,也可以不设置(默认设置None)
        如果不设置daemon,就取当前线程的daemon来设置它
        主线程是non-daemon线程,即daemon=False
        从主线程创建的所有线程的不设置daemon属性,则默认都是daemon=False,也就是non-daemon线程
        python程序在没有活着的non-daemon线程运行时退出,也就是剩下的只能是daemon线程,主线程才能退出,否则主线程只能等待
    举例1:
        import time
        import threading
    
        def bar():
            time.sleep(10)
            print('bar')
    
        def foo():
            for i in range(20):
                print(i)
    
            t = threading.Thread(target=bar, daemon=False)
            t.start()
    
        #主线程是non-daemon线程
    
        t = threading.Thread(target=foo, daemon=True)
        t.start()
    
        #time.sleep(2)  # 让主线程等待2秒后执行,子线程才可以执行到
        print('Main Thread Exiting')
    
        上例中,不会输出bar这个字符,因为主线程执行完成后没有到等
        
    举例2:    
        import time
        import threading
    
        def foo(n):
            for i in range(n):
                print(i)
                time.sleep(1)
    
    
        t1 = threading.Thread(target=foo, name='t1', args=(20,), daemon=True)
        t1.start()
    
        t2 = threading.Thread(target=foo, name='t2', args=(10,), daemon=False)
        t2.start()
    
        time.sleep(2)
        print('Main Thread Exiting')    
            
    上例说明,如果有non-daemo线程的时候,主线程退出时,也不会杀掉所有daemon    线程,
    直到所有non-daemon线程全部结束,如果还有daemon线程,主线程需要退出,会结束所有daemon线程退出

    3、join方法

    • join(timeout=None),是线程的标准方法之一
    • 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止
    • 一个线程可以被join多次。timeout参数指定调用者等待多久没有设置时,就一直等待被调用者线程结束
    • 调用谁的join方法,就是join谁,就要等谁
    举例1:
        import time
        import threading
    
        def foo(n):
            for i in range(n):
                print(i)
                time.sleep(1)
        t1 = threading.Thread(target=foo, args=(10,), daemon=True)
        t1.start()
        t1.join()
        print('Main Thread Exiting')
    
        使用了join方法后,daemon线程执行完了,主线程才退出
        


    4、daemon线程应用场景

    •  简单来说,本来并没有daemon thread,为了简化程序员工作,让他们不用去记录和管理那些后台线程,创造了一个daemon thread的概念
    •  这个概念唯一的作用就是当你把一个线程设置为daemon,它会随主线程的退出而退出
    •  主要应用场景有:
      •  1、后台任务,如发送心跳包,监控,这种场景最多
      • 2、主线程工作才有用的线程,如主线程中维护这公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适
      • 3、随时可以被终止的线程;如果主线程退出,想所有其他工作线程一起退出,就使用daemon=true来创建工作线程
    举例1:
        import time
        import threading
    
        def bar():
            while True:
                time.sleep(1)
                print('bar')
    
        def foo():
            print("t1 daemon = {}".format(threading.current_thread().isDaemon()))
            t2 = threading.Thread(target=bar)
            t2.start()
            print("t2 daemon = {}".format(t2.isDaemon()))
    
    
        t1 = threading.Thread(target=foo, daemon=True)
        t1.start()
    
        time.sleep(3)
        print('Main Thread Exiting')


    5、threading.loacl类

    举例1:
        import threading
        import time
    
        #局部变量实现
        def worker():
            x = 0
            for i in range(100):
                time.sleep(0.0001)
                x += 1
            print(threading.current_thread(),x)
    
        for i in range(10):
            threading.Thread(target=worker).start()
            
    上例使用多线程,每个线程完成不同的计算任务,x是局部变量,能否改造成使用全局变量完成
    
    
    举例2:
    
        import threading
        import time
    
        class A:
            def __init__(self):
                self.x = 0
    
        globals_data = A()
    
        def worker():
            globals_data.x = 0
            for i in range(100):
                time.sleep(0.0001)
                globals_data.x += 1
            print(111111,threading.current_thread(), globals_data.x)
    
        for i in range(10):
            threading.Thread(target=worker).start()
    
    上例虽然使用了全局对象,但是线程之间互相干扰,线程存储的数据可以被其他线程看下,导致了错误的结果
    
        
    举例3:
    
        import threading
        import time
    
        globals_data = threading.local()  
    
        def worker():
            globals_data.x = 0
            for i in range(100):
                time.sleep(0.0001)
                globals_data.x += 1
            print(111111,threading.current_thread(), globals_data.x)
    
        for i in range(10):
            threading.Thread(target=worker).start()
    
        python提供threading.local类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象
        存储的数据其他线程看不见
        上例结果和局部变量的效果一样
        
    举例4:
    
        import threading
    
        X = 'abc'
        ctx = threading.local()  #注意这个对象所处的线程
        ctx.x = 123
    
        print(ctx, type(ctx), ctx.x)
    
        def worker():
            print(X)
            print(ctx)
            print(ctx.x)
            print('working')
    
        worker()   #普通函数调用
        print()
        threading.Thread(target=worker).start()  #另起一个线程
        
        从运行结果来看,另起一个线程执行ctx.x这句报错了
        但是,ctx打印没有出错,说明看到ctx,但是ctx中的x看不到,这个x不能跨线程
    
    threading.local类构建了一个大字典,其元素是每一个线程实例的地址为key和线程对象引用线程单独的字典的映射
    通过threading.local实例就可以在不同的线程中,安全地使用线程独有的数据,做到了线程间的数据隔离,如同本地变量一样安全

    六、定时器Timer/延迟执行

    • threading.Timer继承自Thread,这个类用来定义多久执行一个函数:class threading.Timer(interval,function,args=None,kwargs=None)
    • start方法执行之后,Timer对象会处于等待状态,等待了interval之后,开始执行function函数;如果在执行函数之前的等待阶段,使用了cancel方法,就会跳过执行函数结束
    举例1:
    
        import threading
        import time
        import logging
    
        FORAMT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
        logging.basicConfig(format=FORAMT, level=logging.INFO)
    
        def worker():
            logging.info('in worker')
            time.sleep(2)
    
        t = threading.Timer(5,worker)
        t.setName('w1')
        t.start()  # 启动线程
        print(111,threading.enumerate())
        t.cancel()  #取消,可以注释这一句看看如何定时执行
        time.sleep(1)
        print(threading.enumerate())    
    
    
        Timer是线程Thread的子类,就是线程类,具有线程的能力和特征
        它的实例是能够延时执行目标函数的线程,在真正执行目标函数之前,都可以cancle它


    七、线程同步

    • 线程同步是线程间协同,通过某种技术,让一个线程访问某些数据是,其他线程不能访问这些数据,直到该线程完成对数据的操作
    • 临界区(Critical Section),互斥量(Mutex),信号量(Semaphore), 事件Event

    1、Event

    • Event事件是线程间通信机制中最简单的实现,使用一个内部的标记flag,通过flag的True或False的变化来进行操作
    • set(): 标记设置为True
    • clear() : 标记设置为False
    • is_set() : 标记是否为True
    • wait(timeout=None) : 设置等待标记为True的时长,None为无限等待,等到返回True,未等到超时了返回False
    例如:老板雇佣了一个工人,让他生成杯子,老板一直等着工人,直到生成了10个杯子
    
    举例1:
        from threading import Event, Thread
        import logging
        import time
    
        FORAMT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
        logging.basicConfig(format=FORAMT, level=logging.INFO)
    
        def boss(event:Event):
            logging.info("I'm boss, waiting for u")
            #等待
            event.wait()
            logging.info("Good Job")
    
        def worker(event:Event, count=10):
            print(111,event)
            logging.info("I'm working for U")
            cups = []
            while True:
                logging.info('make 1')
                time.sleep(0.5)
                cups.append(1)
                if len(cups) >= count:
                    # 通知已完成
                    event.set()  # 标记为True,结束boss等待
                    break
            logging.info("I finished my job. cups={}".format(cups))
    
        event = Event()
        w = Thread(target=worker, args=(event,))
        b = Thread(target=boss, args=(event,))
        w.start()
        b.start()
    
        使用同一个Event对象的标记flag,谁wait就是等到flag变为True,或等到超时返回False,不限制等待的个数


    2、wait的使用

    from threading import Event, Thread
        import logging
        import time
    
        FORAMT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
        logging.basicConfig(format=FORAMT, level=logging.INFO)
    
        def do(event:Event, interval:int):
            while not event.wait(interval):  #返回True或者False
                logging.info('do sth')
    
        e = Event()
        Thread(target=do, args=(e, 3)).start()
        e.wait(10)
        e.set()
        print('main exit')
        
        wait优于sleep,wait会主动让出时间片,其他线程可以被调度,而sleep会占用时间片不让出


    3、Event练习

    实现Timer,延时执行的线程,延时计算add(x,y)
        思路:Timer的构造函数中参数得有哪些,如何实现start启动一个线程执行函数,如何cancel取消待执行任务
    
        from threading import Event, Thread
        import logging
        import time, datetime
        logging.basicConfig(level=logging.INFO)
    
        def add(x:int, y:int):
            logging.info(x + y)
    
        class Timer:
            def __init__(self, interval, fn, *args, **kwargs):
                self.interval = interval
                self.fn = fn
                self.arge = args
                self.kwargs = kwargs
                self.event = Event()
    
            def start(self):
                Thread(target=self.__run).start()
    
            def cancel(self):
                self.event.set()
    
            def __run(self):
                start = datetime.datetime.now()
                logging.info('waiting')
    
                self.event.wait(self.interval)
                if not self.event.is_set():  # 标记是否为Ture
                    self.fn(*self.arge, **self.kwargs)
                delta = (datetime.datetime.now() - start).total_seconds()
                logging.info('Finished {}'.format(delta))
    
        t = Timer(10,add,4,50)
        t.start()
        e = Event()
        e.wait(4)
  • 相关阅读:
    动态规划算法
    Spring依赖循环
    使用JMH微基准测试
    expect介绍和使用
    autossh使用(本机记住ssh密码)
    ssh端口转发(ssh隧道)
    WSL2中的Centos8安装桌面
    Tmux Plugin Manager使用及具体插件
    Python使用os.chdir命令切换python工作目录
    python脚本要控制jenkins触发job
  • 原文地址:https://www.cnblogs.com/jiangzuofenghua/p/11450644.html
Copyright © 2020-2023  润新知