• chapter11.1、并发,进程,线程


    并发

    并发和并行

    并行,parallel,某时刻,同时做几件事

    并发,concurrency,时间段内有事情要处理

    并发解决方案

    并发必须解决,所有程序几乎都要面对,特别是面对用户是互联网用户时,高并发更需要处理

    打饭模型,中午十二点,食堂涌入大量人,如果人很多,这就是高并发。

    1,队列,排队,

    假设资源只有一个,队列或者列表是较好的方式

    可以使用优先队列或者双队列

    队列就是缓冲区buffer,代价较小,但是在面对高并发时可能会不够用

    作用是平滑请求

    例如queue模块的类,Queue,LifoQueue,PriorityQueue

    2、争抢

    挤到窗口的,会一直占据着窗口,可以视为锁定窗口,不在为其他人服务,视为一种锁机制

    争抢是一种排他性的锁,其他人只能等。

    对于某些请求者,可能永远抢不到资源

    有争抢就要有锁

    3、预处理

    如果排长队的原因,是由于每个人的等候时间太长,就要考虑提前将热门的80%的饭菜做好,保证供应,其他的现做,这样就算锁定窗口,也能很快就释放。

    提前加载用户需要的数据的思路,就是预处理思想,缓存常用。

    高并发常用解决思想,大规模时使用分布式缓存

    4、并行

    分配问题,轮询(最简单的模型),要考虑队伍长短的解决方案,可以实时看队列的情况来看放入那个队列。

    开窗口就像扩大食堂,会造成成本上升

    日常可以通过购买更多服务器,或者开多线程,多进程并行实现

    水平扩展思想

    线程在单cpu时,就不是并行了

    并行只是并发的解决方案之一

    5、提速

    提高打饭速度,也是解决方案

    提高单个CPU性能,或者服务器安装更多cpu

    垂直扩展思想

    6、消息中间件

    地铁站外的安检走廊,缓冲人流,进去后还要重新排队

    队列作用是缓冲,解耦

    常用第三方队列,也就是消息中间件,常用的有RabbitMQ、ActiveMQ(Apache)、RocketMQ(阿里Apache)、kafka(Apache)等

    解决高并发的方法还有很多,一般会根据不同的场景用不同的策略,而策略可以是多种方式的优化组合。

    可以多开食堂(多地),也可以就近,建到宿舍附近(就近),技术来源生活。

    线程,进程

    实现了线程的操作系统中,线程是操作系统能够运算调度的最小单位。线程包含在进程中,是进程的实际运作单位,一个程序的执行实例就是进程。

    进程是计算机中程序关于某数据集合的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统的基础。 

      ABI (Application Binary Interface)  应用程序二进制接口

    程序源代码编译后的文件存放在磁盘中,当程序被操作系统加载到内存中时,就是进程,进程中存放着指令和数据(资源),进程也是线程的容器

    Linux中有父进程,子进程,Windows的进程是平等关系

    操作系统必须有进程管理

    线程 有时被称为轻量级进程LWP(Lightweight Process)是程序执行流的最小单元

    一个标准的线程由线程的ID,当前指令指针(PC:程序计数器),寄存器集合和堆栈组成。

    理解为:

    进程就是独立的王国,不会共享数据

    线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程有自己的数据,独立的堆栈

    线程状态

    ready  就绪 线程能够运行,但在等待调度

    running  运行 线程正在运行

    blocked  阻塞 线程等待外部事件的发生而无法运行,如I/O操作

    terminal  终止,线程完成或者退出,或者取消

    以上是较常见的几种状态,不同的操作系统可能会不同

    线程的状态转换

    虚拟化:把一个资源当做多个资源用

    分时的想法,将cpu的运算时间细分,时间切分的十分的小,几微秒处理一个线程,让人感觉像是同时在处理。可以加入优先的思想,让人感觉这个处理的较快,这些全部由操作系统调度。这里就利用了虚拟化的思想。

    时间片

    多核,可以同时执行;单核,基本都是分时的。

    Python的线程开发

    Python的进程会启动解释器进程,线程共享一个解释器进程

    Python线程的开发使用标准库threading

    Thread类

    #签名
    def __init__(self, group=None, target=None, name=None,
                     args=(), kwargs=None, *, daemon=None):

    参数:

    target  线程调用的函数,就是目标函数

    name  线程的名字

    args  目标函数的参数,实参,要求必须是元组

    kwargs  为目标函数关键字传参,字典

    线程目标创建后并不会启动,要使用start方法

    线程就是执行代码的,函数是最简单的封装,函数执行完就退出,如果要不退出,就要使用while

    import threading,time
    
    def worker(x,y):
        count = 0
        while count < 5:
            print('I am working',x,y)
            time.sleep(2)
            count += 1
        print('finish')
    
    t = threading.Thread(target=worker,name='work1',args=(4,),kwargs={'y':2})
    
    t.start()
    print('+++end+++')

    线程退出

    python没有提供线程退出的方法,结束的方法就是语句执行完或者抛出未处理的异常

    python的线程没有优先级,不能被销毁,停止,挂起,也没有恢复,中断

    线程的传参和函数的传参相同。

    threading的属性方法

    current_thread()  返回当前线程对象

    main_thread()  返回主线程对象

    active_thread()  返回处于alive状态的线程的个数

    enumerate()  返回所有或者的线程的列表,不包括已经终止的线程和未开始的线程

    get_ident()  返回当前线程的ID,非0整数

    threading 实例的属性和方法

    name   名字,标识,可以重名,getaName()、setName()获取设置这个名词

    ident  线程ID,非零整数,线程启动才有ID,未启动未None。线程退出后,此ID依旧可以访问,此ID可以重复使用。注意ID只有线程退出后才能在利用,线程运行时ID唯一。

    is_alive  返回线程是否活着

    同一个线程只能被调用一次,再使用就重新创建

    t.start() 启动线程,只能用一次,创建线程对象,start会调用run

    t.run() 运行线程函数,在当前线程执行,顺序执行就是在主线程中调用了一个普通的函数而已

    run不会开启新的线程,start会启动一个新线程。

    启用线程使用start方法,才能启动多个线程。

    串行化,并行化,对于硬件设备,并口速度快于串口,但是成本增高,并且处理难度上升。所以一般都使用串口。系统的进程都会开启多线程,但是开启越多,需要的管理成本,维护成本越高。所以线程开启合适的数目就好,并不是多多益善。

    串口形容一下就是 一条车道,而并口就是有8个车道同一时刻能传送8位(一个字节)数据。 但是并不是并口快,由于8位通道之间的互相干扰。传输时速度就受到了限制。而且当传输出错时,要同时重新传8个位的数据。串口没有干扰,传输出错后重发一位就可以了。所以要比并口快。

    多线程

    主线程,一般来协调管理,工作线程工作,一般是这样

    当使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程。

    一个进程至少有一个线程,并作为程序的入口,这个线程就是主线程。进程至少有一个主线程

    函数在不同的线程中压栈,每次调用都是不同的对象。

    线程安全

    在IPython中演示可以看到效果,python命令行,pycharm都不能

    import threading
    
    def worker():
        for x in range(100):
            print("{} 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()

    产生以上问题的原因,就是在print函数执行时,函数是分两段执行的,先打印内容,再打印换行,在这之间发生了线程的切换

    这说明print函数是线程不安全的

    线程执行一段代码,不会产生不确定结果,那么这段代码就是线程安全的

    解决可以使print打印字符串这个不可变的整体,而将print换行,或者使用logging模块,日志处理模块,生产环境代码都使用logging

    import threading
    import logging,time
    
    def worker():
        for x in range(10):
            time.sleep(0.3)
            #print("{} is running.
    ".format(threading.current_thread().name), end='')
            logging.info("{} 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()

    logging info 级别低于经警告

    daemon线程和non-daemon线程

    deamon不是Linux中的守护进程,有翻译之为后台进程,但感觉不够准确。

    主线程退出时,看工作线程的daemon,daemon为False就等待。

    只在主线程结束时才看daemon,有non-daemon就等待non-daemon所在的线程结束后结束主线程。

    主线程是第一个启动的线程

    父线程:如果线程A中启动了线程B,A就是B的父线程

    子线程:B就是A的子线程

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

    没给定deamon,用父线程的deamon,python中在构建Thread时,会根据是否提供daemon,来判断是否是daemon线程

    #源码中__init__方法中
    if
    daemon is not None: self._daemonic = daemon #用户传入的bool值 else: self._daemonic = current_thread().daemon
    import threading,time
    
    def foo(n):
        for i in range(n):
            print(i)
            time.sleep(1)
    
    
    t1 = threading.Thread(target=foo,args=(10,),daemon=True)
    t1.start()
    
    t2 = threading.Thread(target=foo,args=(20,),daemon=False)
    t2.start()

    daemon属性    表示线程是否时daemon属性,这个值必须在start之前设置,否则引发RuntimeError异常

    isDaemon()   是否是daemon线程

    setDaemon  设置为daemon线程,必须在start方法之前设置

    要点:

      线程都有daemon属性,不设置默认为None,设置为True 或者False

      不设置daemon,就取父线程的daemon来设置

      主线程是non-daemon线程,即daemon = False

      从主线程创建的所有线程,不设置dameon就都会取主线程的daemon,也就是non-daemon。

      python中主线程只有在没有活着的non-daemon时才会退出,否则只有等待。

    daemon的应用场景

    daemon 的出现可以简化程序员工作,让他们不用去记录和管理那些后台线程。

    这个daemon概念,就是可以把线程设置为随主线程结束而结束的线程。

    主要应用场景有:

      1、后台任务,如发送心跳包、监控,这种场景最多

      2、主线程工作才有用到线程,应当随主线程的结束而结束,比如主线程中的公共资源,如果主线程退出,那么工作线程使用的资源也就没有用了,一起随主线程结束。

      3、随时可以被终止的程序,如果主线程退出,想要其他工作线程一起退出。比如开启线程定时判断WEB情况的线程,没有必要一直记得,只要设置成daemon,就可以在关闭主线程是结束它

     这样可以简化程序员手动关闭线程。

     

    join 方法

    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")

    上例中,主线程调用t1的join后,主线程被阻塞了,等daemon线程执行完了,主线程才退出。

    join(timeout=None)是线程的标准方法,

    一个线程可以被join多次

    一个线程调用另一个线程的join,就是调用者被阻塞,直到被调用的线程结束。

     timeout可以指定调用者等待的时间,没有设置就是None,一直等到线程结束。

    调用谁的join方法,就是要在调用的线程里等谁。

    如果在主线程中,join 方法可以使daemon失效,即使是daemon线程,也要等到运行完才结束。

    threading.local类

    import time
    import threading
    
    class A:
        def __init__(self):
            self.x = 0
    global_data = A()
    
    def work():
        #x = 0     ## x不是全局的,能实现分别计算
        #global x  ## 全局变量下,会在一个变量上一直计算
        global_data.x = 0      ##等同于上一个,该实例的属性x只有一个
        for i in range(100):
            time.sleep(0.0001)
            global_data.x += 1
        print(threading.current_thread(),global_data.x)
    
    for i in range(10):
        threading.Thread(target=work).start()
    
    print("Main Thread Exiting")

    使用多线程,每个线程完成不同的计算任务,x是局部变量时,互不干扰

    如果将x设置为全局变量就会相互干扰

    python提供了threading.local类,将这个实例化得到一个全局对象,对于不同的线程使用该对象存储的数据对其他线程不可见。

    import time
    import threading
    
    global_data = threading.local()
    
    def work():
        global_data.x = 0      #每个线程调用的对象都不是同一个,只是名字相同
        for i in range(100):
            time.sleep(0.0001)
            global_data.x += 1
        print(threading.current_thread(),global_data.x)
    
    for i in range(10):
        threading.Thread(target=work).start()
    
    print("Main Thread Exiting")

    执行结果和使用局部变量的效果是一样的。

    log = []
    mydata = threading.local()
    mydata.x = 123
    def f():
        items = sorted(mydata.__dict__.items())
        log.append(items)
        mydata.number = 11
        log.append(mydata.number)
        print(mydata.number)
        print(mydata.x)  ##会报错,线程不共享该local对象在其他线程的属性,该属性与该线程绑定
    
    import threading
    thread = threading.Thread(target=f)
    thread.start()
    thread.join()
    print(log)
    print(mydata.number)   ##会报错,线程不共享其他线程内对象的属性

    定时器 Timer延迟执行

    类,延迟执行,继承自Thread,用来定义延迟多久之后执行一个函数

    Timer提供了cancel方法,重写了run函数,就是Thread的子类,就是线程类,

     cancal方法本质上是Event类实现,只有线程执行的目标函数未执行前,才能cancal

  • 相关阅读:
    【力扣 089】24. 两两交换链表中的节点
    【力扣 086】19. 删除链表的倒数第 N 个结点
    【力扣 085】2. 两数相加
    【力扣 093】92. 反转链表 II
    【力扣 090】25. K 个一组翻转链表
    【力扣 091】61. 旋转链表
    【力扣 088】23. 合并K个升序链表
    【力扣 087】21. 合并两个有序链表
    【力扣 092】82. 删除排序链表中的重复元素 II
    vim命令
  • 原文地址:https://www.cnblogs.com/rprp789/p/9763676.html
Copyright © 2020-2023  润新知