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











     

     

     

     

     

     

  • 相关阅读:
    MSDN Magazine搞错了
    Visual Studio 2005中设置调试符号(Debug Symbols)
    BCB 6的问题
    吴裕雄天生自然Spring Boot使用Spring Data JPA实现人与身份证的一对一关系映射
    吴裕雄天生自然Spring BootSpring Data JPA
    吴裕雄天生自然Spring BootSpring Boot对JSP的支持
    吴裕雄天生自然Spring BootSpring Boot的异常统一处理
    吴裕雄天生自然Spring Boot使用Spring Data JPA实现Author与Article的一对多关系映射
    吴裕雄天生自然Spring Boot解决 Error creating bean with name 'entityManagerFactory' defined in class path resource
    吴裕雄天生自然Spring Boot@ExceptionHandler注解和@ControllerAdvice注解
  • 原文地址:https://www.cnblogs.com/komorebi/p/10976330.html
Copyright © 2011-2022 走看看