zoukankan      html  css  js  c++  java
  • 36 线程 队列 守护线程 互斥锁 死锁 可重入锁 信号量

    线程

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

    线程和进程的关系

    线程不能单独存在 必须存在于进程中,

    进程是一个资源单位,其包含了运行程序所需的所有资源

    线程才是真正的执行单位

    没有线程,进程中的资源无法被利用起来,所以一个进程至少包含一个线程,称之为主线程

    当我们启动一个程序时,操作系统就会自己为这个程序创建一个主线程

    线程可以由程序后期开启 ,自己开启线程称之为子线程

    为什么需要线程

    目的只有一个就是提高效率

    就像一个车间 如果产量跟不上 就再造一条流水线

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

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

     

     

    特点:

    1.每个进程都会有一个默认的线程

    2.每个进程可以存在多个线程

    3.同一进程中的所有线程之间数据是共享的

    4.创建线程的开销远比创建进程小的多

    主线程与子线程的区别:

    1.线程之间是没有父子之分,是平等的

    2.主线程是由操作系统自动开启的,而子线是由程序主动开启

    3.即使主线程的代码执行完毕,也不会结束进程,会等待所有线程执行完毕,进程才结束

    JoinableQueue队列

    from multiprocessing import JoinableQueue
    # 可以被join的队列

    q = JoinableQueue()

    print('------------')
    q.put('123')
    q.put('456')

    print('取走了%s'% q.get())

    q.task_done()
    # 告诉队列这个数据已经被处理完毕
    # 而不是表示任务全部处理完成
    # 只是取出某个数据处理完成
    print('-----------')
    print('取走了%s'% q.get())
    q.task_done()
    q.join()
    # 等待队列中的数据被处理完毕
    print('over')

    # task_done=put 调用次数相等 进程才会结束

    生产者消费者模型
    import random
    import time
    from multiprocessing import Process,Queue ,JoinableQueue


    def make_ice_cream(name,q):
    for i in range(5):
    time.sleep(random.randint(1,3))
    print("%s生产了冰激凌%s" % (name, i))
    q.put('%s的%s号冰激凌'%(name,i))



    def eat_ice_cream(name,q):
    while True:
    ice_cream=q.get()
    # if not ice_cream:
    # break
    time.sleep(random.randint(1,3))
    print('%s吃掉了%s'%(name,ice_cream))
    # 必须记录该数据处理完成了
    q.task_done()


    if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=make_ice_cream,args=('阿三的冰激凌店',q))
    p2 = Process(target=make_ice_cream, args=('阿肆的冰激凌店', q))
    p3 = Process(target=make_ice_cream, args=('阿五的冰激凌店', q))

    c1 = Process(target=eat_ice_cream,args=('大王',q))
    c1.daemon=True
    c2 = Process(target=eat_ice_cream, args=('二王', q))
    c2.daemon=True
    c3 = Process(target=eat_ice_cream, args=('三王', q))
    c3.daemon=True

    p1.start()
    p2.start()
    p3.start()

    c1.start()
    c2.start()
    c3.start()

    # 目前的思路 是当商家做完以后 放一个None 作为结束标志 而且 必须明确商家和消费者的个数
    # 明确商家生成完毕 再明确消费者吃完了 就算结束
    p1.join()
    print("第一家生成完毕")
    p2.join()
    print("第二家生成完毕")
    p3.join()
    print("第三家生成完毕")

    # 消费者吃完了
    q.join()
    print('消费者吃完了')
    创建线程的俩种方式
    from threading import Thread, current_thread
    import time


    # current_thread:当前线程

    def task():
    print('1', current_thread())
    print('子线程running')
    time.sleep(5)
    print('子线程over')


    # 方法一:直接实例化Thread类
    if __name__ == '__main__':
    t = Thread(target=task)
    t.start()

    task()
    print('主线程over')
    print('1', current_thread())


    # 执行顺序不固定 如果开启线程速度足够快 可能子线程先执行
    # 方法2

    class MyThread(Thread):
    def run(self):
    print('子线程run')


    m = MyThread()
    m.start()
    print('主线over')

    # 使用方法和多进程一模一样 开启线程的代码可以放在任何位置 开启进程必须放在判断下面

    线程与进程区别:

    1.同一进程中 线程之间数据共享

    a = 100
    def task():
       global a
       print("子线程 run........")
       a = 1

    t = Thread(target=task)
    t.start()

    print(a) # 1
    print("over")

    2.创建线程的开销远比创建进程小的多

    from threading import  Thread
    from multiprocessing import  Process
    import time

    def task():
       pass

    if __name__ == '__main__':
       start = time.time()
       for i in range(100):
           p = Thread(target=task)
           p.start()
       print(time.time()-start)
    # 修改Thread 为Process类 查看结果

    3.无论开启了多少子线程PID是不会变的

    from threading import  Thread
    import os

    def task():
       print(os.getpid())

    for i in range(100):
       p = Thread(target=task)
       p.start()

    Tread类的常用属性:

    # threading模块包含的常用方法
    import threading
    print(threading.current_thread().name) #获取当前线程对象
    print(threading.active_count()) # 获取目前活跃的线程数量
    print(threading.enumerate()) # 获取所有线程对象


    t = Thread(name="aaa")
    # t.join() # 主线程等待子线程执行完毕
    print(t.name) # 线程名称
    print(t.is_alive()) # 是否存活
    print(t.isDaemon()) # 是否为守护线程

    守护线程

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

    # 主线程代码执行完毕后,不会立即结束,会等待其他子线程结束
    # 主线程 会等待非守护线程结束后结束
    # 如果守护线程已经完成任务,程序立马结束
    ef task():
    print('子线程1 跑........')
    time.sleep(2)
    print('子线程1 关.....')


    def task2():
    print('子线程2 跑........')
    time.sleep(2)
    print('子线程2 关.....')


    t1=Thread(target=task)
    t1.daemon=True
    t1.start()



    t2=Thread(target=task2)
    t2.start()

    print('主线程 关.....')

    顺序是:守护线程 等待 主线程 等待 其余子线程

    线程 互斥锁

    多线程的最主要特征之一是:同一进程中所有线程数据共享

    一旦共享必然出现竞争问题。

    线程中也存在安全问题,

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

    解决方案:还是互斥锁

    案例:

    rom threading import Thread, enumerate, Lock

    import time

    number = 10
    lock = Lock()


    def task():
    global number
    lock.acquire()
    a = number
    time.sleep(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)

    死锁问题

    死锁问题
    当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁
    这时候程序就会进程无限卡死状态 ,这就称之为死锁
    例如:
    要吃饭 必须具备盘子和筷子 但是一个人拿着盘子 等筷子 另一个人拿着筷子等盘子

    如何避免死锁问题
    锁不要有多个,一个足够
    如果真的发生了死锁问题,必须迫使一方先交出锁

    例子:

    现有两把锁l1和l2 用于表示盘子和筷子

    两个线程的目标是吃饭,要吃饭的前提是同时拿到筷子和盘子,但是两个人的目标不同一个先拿筷子 ,一个先拿盘子最终造成死锁

    from threading import Lock, current_thread, Thread

    # 盘子
    lock1 = Lock()
    # 筷子
    lock2 = Lock()


    def eat1():
    lock1.acquire()
    print("%s抢到了盘子" % current_thread().name)
    lock2.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)


    def eat2():
    lock2.acquire()
    print("%s抢到了筷子" % current_thread().name)
    lock1.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)


    t1 = Thread(target=eat1)
    t2 = Thread(target=eat2)

    t1.start()
    t2.start()

    可重入锁

    Rlock  
    称之为递归锁
    或者可重入锁
    Rlock不使用用来解决死锁问题的

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

    如果一个线程已经执行过acquire 其他线程将无法执行acquire
    Rlock仅仅是帮你解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题
    正常锁
    from threading import RLock,Lock,Thread
    l=Lock()
    l.acquire()
    print('1111111')
    l.release()
    print('222222222')
    死锁
    from threading import RLock,Lock,Thread
    l=Lock()
    l.acquire()
    print('111111')
    l.acquire()
    print('2222222')
    在处理并发安全时 用完公共资源后一定要释放锁

    信号量

    Semaphore

    信号量也是一种锁,其特殊之处在于可以让一个资源同时被多个线程共享,并控制最大的并发访问线程数量。

    Lock 锁住一个马桶  同时只能有一个
    Semaphore 锁住一个公共厕所 同时可以来一堆人


    用途: 仅用于控制并发访问 并不能防止并发修改造成的问题
    from threading import Semaphore,Thread
    import time

    a=Semaphore(5)
    def task():
    a.acquire()
    print('子线程。。')
    time.sleep(3)
    print('主线程。。')
    a.release()


    for i in range(8):
    t=Thread(target=task)
    t.start()











     

     

     

     

     

     

  • 相关阅读:
    Oracle中的rownum
    关于Oracle12c中无scott用户的问题
    docker 1.13 是什么版本?
    ImportError: cannot import name 'SQLALchemy'
    mac配置iterm2,iterm2使用方法
    docker 时区
    jupyter 500 : Internal Server Error
    sublime conda 切换环境
    mac frida安装(使用网易木木模拟器)
    \u开头两个字符的是什么编码?
  • 原文地址:https://www.cnblogs.com/komorebi/p/10976330.html
Copyright © 2011-2022 走看看