• JoinableQueue队列,线程,线程于进程的关系,使用线程,线程的特点,守护线程,线程的互斥锁,死锁问题,递归锁,信号量


      1.JoinableQueue队列

    JoinableQueue([maxsize]):这就像是一个Queue对象,但是队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

    案例:

    from multiprocessing import JoinableQueue
    # join是等待某个任务完成 able 可以  Queue 队列
    # 翻译过来被join的队列
    q = JoinableQueue()
    
    q.put('123')
    q.put('456')
    print('取走一个了%s'% q.get())
    q.task_done()
    # 可以当作是普通的Queue的队列来使用
    
    
    # task_done方法,是告诉队列这个数据已经被处理完了
    # 需要注意的是,此处的处理完的并不是任务全部处理完成,
    # 而指的是当前取出的数据已经处理完成。
    # 所以每一个get 需要对应一个 task_done
    # task_done 不能超过get的次数,否则会报多次掉用的错误
    print('又取走了一个%s'% q.get())
    q.task_done()
    
    
    # 如果队列中的值已经被取空了,再次使用get取值会卡死
    # 因为他在一直在等待有人往容器里存值
    # print('又取走了一个%s'% q.get())
    # q.task_done()
    
    
    # join方法,等待队列中的数据被处理完毕,
    # 一般在使用的时候,join 与 task_done 的次数 == put 的调用次数
    q.join()
    print('结束')

      2. 线程:

    什么是线程:

      线程是操作系统最小的运算调度单位,被包含在进程中,一个线程就是一个固定的执行流程

      3.线程于进程的关系 ***** (重点)

      线程是不能单独存在的,必须存在进程中,进程是一个资源单位,其包含了运行程序所需的所有资源

      线程才是真正的执行单位,没有线程,进程中的资源就无法被利用起来,所以一个进程至少包含一个线程,这个线程就称之为主线程

      当我们启动一个程序时,操作系统就会自己为这个程序创建一个主线程,而由程序后期开启的线程,就称之为子线程

    4.使用线程:

    在使用之前,首先要想一个问题,为什么需要使用线程?

      其实目的只有一个,那就是提高效率

    如果把进程比喻成车间,那么线程就是车间的流水线。

    我们提高效率,当然可以再造一个新车间,那需要把原材料运过去 ,这个过程是非常耗时的

    所以通常情况是创建新的流水线 而不是车间   

    怎么使用?

      使用方法和多进程是一模一样的,只是使用了不同的模块而已

    模块名:threading

    类名:Thread

    不一样的是,开启线程是不需要放在__main__下面,因为他们共用同一个进程的数据,而进程是将所有的数据全都重新拷贝了一份

    案例:

    第一种,直接实例化使用

    from threading import Thread
    
    # 第一种。直接调用Thread模块
    
    def task():
        print('子进程开启')
        print('子进程结束')
    
    
    t1 = Thread(target=task)
    
    t1.start()
    print('主线程结束')

    第二种继承Thread,然后覆盖run方法

    # 第二种,继承Thread类,覆盖run方法
    class MyThread(Thread):
        def run(self):
            print('子线程开启')
            print('子线程结束')
    
    
    t = MyThread()
    t.start()
    print('主线程结束')

    5.线程的特点

      1.创建开销小,不用像进程需要导入数据

      2.同一个进程中的多个线程数据共享

      3.多个线程之间是平等的,没有父子关系。

    案例1:同一进程中的线程数据共享

    # 证明同一进程中的线程共享数据
    
    from threading import Thread
    
    # 定义一个全局变量
    a = 10
    def task():
        global a
        print('这是子线程')
        a = 20
    
    t1 = Thread(target=task)
    
    t1.start()
    t1.join()
    
    print(a)  # 此处的值已经被修改了,加入是在子进程中,是不会被修改的
              # 因为进程之间的数据并不互通

    案例二:程序线程的开销小于进程

    import os, time
    from threading import Thread
    from multiprocessing import Process
    
    def task():
        pass
    
    if __name__ == '__main__':
        st_time = time.time()
    
        ts = []
        for i in range(100):
            # t = Thread(target=task)  # 线程输出结果0.01894855499267578 (不固定,因设备而定)
            t = Process(target=task)   # 进程输出结果9.261330604553223 (不固定,因设备而定)
            t.start()
            ts.append(t)
        for t in ts:
            t.join()
    
        # 使用当前时间减去记录的时间会得到开启线程所用的时间
        print(time.time() - st_time)
    
        print('主线程结束')

    6.守护线程

    一个线程可以设置为另一个线程的守护线程

    特点: 被守护线程结束后守护线程也随之结束

    案例:

    from threading import Thread
    import time
    
    def task1():
        print('第一子进程开始')
        time.sleep(2)
        print('第一子进程结束')
    
    def task2():
        print('第二子进程开始')
        time.sleep(1)
        print('第二子进程结束')
    
    t = Thread(target=task1)
    t.daemon = Thread
    t.start()
    
    t1 = Thread(target=task2)
    t1.start()
    
    print('主线程结束')
    # 上述案例是将 t 线程作为守护线程
    # 运行顺序是:
        # 第一子进程开始
        # 第二子进程开始
        # 主线程结束
        # 第二子进程结束
    
    # 主线程代码执行完毕后。不会立即结束,会等待其他子线程结束,主线程会等等待非守护线程 (即t2)
    # 主线程会等待所有非守护线程结束后结束
    
    # 守护线程会等到所有非守护线程结束后结束,如果守护线程已经完成任务,守护线程会立即结束
     主线程代码执行完毕后。不会立即结束,会等待其他子线程结束,主线程会等等待非守护线程 (即t2)
     主线程会等待所有非守护线程结束后结束
     守护线程会等到所有非守护线程结束后结束,如果守护线程已经完成任务,守护线程会立即结束

     

    7.线程的互斥锁

    共享资源就意味着竞争

    线程中也存在安全问题,

    多线程可以并发执行,一旦并发了并且访问了同一个资源就会有问题

    解决方案:还是互斥锁

    案例:

    from threading import Thread,enumerate,Lock
    import time
    
    number = 10
    lock = Lock()
    
    def task():
        global number
        # 使用互斥锁来锁定资源
        lock.acquire()
        a = number
        time.sleep(0.1)
        number = a - 1
        # 使用完毕后释放
        lock.release()
    
    for i in range(10):
        t = Thread(target=task)
        t.start()
    
    for t in enumerate()[1:]:
        t.join()
    print(number)
    
    # 只要是并发执行,在访问同一资源的时候就会出现问题,所以与进程一样
    # 就需要锁来控制资源

     

    8.死锁问题

    from threading import Lock, current_thread, Thread
    
    '''
    死锁问题
    当程序出现了不止一把锁,分别被不同的线程持有,如果有一个资源要想使用必须同时具备两把锁
    这时候程序就会进入无限卡死状态,这就称之为死锁
    例如:
        要吃饭,必须具备盘子和筷子,但是一个人拿着盘子等筷子。另一个人拿着筷子等盘子
    如何避免死锁问题:
        锁不要有多个,一个足够
        如果真的发生了死锁的问题,必须迫使一方先交出锁
        
    '''
    import time
    
    lock1 = Lock()
    
    lock2 = Lock()
    
    
    def eat1():
        lock1.acquire()
        print('%s抢到了盘子' % current_thread().name)
        time.sleep(0.5)
        lock2.acquire()
        print('%s抢到了筷子' % current_thread().name)
        print('%s开动了' % current_thread().name)
        lock2.release()
        print('%s放下筷子' % current_thread().name)
    
        lock1.release()
        print('%s放下盘子' % current_thread().name)
    
    
    
    def eat2():
        lock2.acquire()
        print('%s抢到了筷子' % current_thread().name)
        time.sleep(0.5)
        lock1.acquire()
        print('%s抢到了盘子' % current_thread().name)
        print('%s开动了' % current_thread().name)
        lock1.release()
        print('%s放下盘子' % current_thread().name)
    
        lock2.release()
        print('%s放下筷子' % current_thread().name)
    
    t1 = Thread(target=eat1)
    
    t2 = Thread(target=eat2)
    
    t1.start()
    t2.start()

     

    9.递归锁

    Rlock 称之为递归锁或者可重入锁

    Rlock不是用来解决死锁问题的

    与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次

    如果一个线程已经执行过acquire 其他线程将无法执行acquire

    from threading import RLock,Lock,Thread
    r = RLock()
    
    def task():
        r.acquire()
        print('子进程开始')
        r.release()
    
    
    # 主线程锁了一次
    r.acquire()
    r.acquire()
    
    
    # 有几次acquire,就需要对应几次release
    r.release()
    r.release()
    
    t1 = Thread(target=task)
    t1.start()

    10.信号量

    '''
    信号量
    Lock Rlock
    可以限制被锁定的代码,同时可以被多少线程并发访问
    Lock 锁住一个马桶,同时只能有一个人
    Semaphore 锁住的是公共厕所,同时可以来一堆人
    用途:仅用于控制并发访问,并不能防止并发修改造成的问题
    '''
    from threading import Semaphore,Thread
    
    import time
    
    # 设置每次访问的线程数量,它自带锁
    s = Semaphore(5)
    def task():
        # 使用锁把代码锁住,让其他进程暂时无法访问资源
        s.acquire()
        print('子run')
        time.sleep(2)
        print('子over')
        s.release()
    for i in range(10):
        t = Thread(target=task)
        t.start()

     总结:

    多线程

    线程是CPU的最小执行单位, 线程本质是一堆代码构成的执行流程

    线程被包含在进程中,

    进程是一个资源单位,其中存储着该程序运行所需所有资源, 可以比喻为一个车间

    线程就是车间中国一条流水线,上面放的是制作产品的具体方法(就是的代码)

     

    一个进程至少有一个线程,操作系统在运行一个程序时 会在进程中自动开启一条线程

    一个进程中可以有多个线程

    同一个进程中的线程共享进程内的数据

    创建线程速度非常快 开销小

    线程之间没有父子关系 都是平等的

     

     

    使用方式和进程一致

    学习多线程 多进程 都是为了提高效率 ,通过并发执行多个任务

    实现并发的方式,多线程和多进程

      

  • 相关阅读:
    git提交到项目中自动部署
    vue纯前端导入导出excel
    VMware Workstation pro14 虚拟机下安装CentOS6.5图文教程
    [正睿集训2021] 杂题再讲
    #4 CF568E & CF613E & CF587D
    Atcoder Grand Contest 009&010
    [省选集训2022] 模拟赛4
    #3 CF536D & CF538G & CF559E
    [省选集训2022] 模拟赛7
    Atcoder Grand Contest 007&008
  • 原文地址:https://www.cnblogs.com/liguodeboke/p/10975869.html
Copyright © 2020-2023  润新知