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.\n".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.\n".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)

猜你喜欢

转载自www.cnblogs.com/jiangzuofenghua/p/11450644.html
今日推荐