zoukankan      html  css  js  c++  java
  • 并发编程-多线程

    多线程

    什么是线程:

    线程指的是一条流水线的工作过程的总称

    线程是CPU的基本执行单位

    ​ 对比进程而言,进程仅仅是一个资源单位其包含了程序运行所需的资源,就像一个车间

    ​ 而单有资源是无法生产出产品的,必须有具体的生产产品的逻辑代码

    ​ 线程就相当于车间中的一条流水线,而你的代码就是流水线上的一道道工序

    特点:

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

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

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

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

    image-20190307060333482

    主线程与子线程的区别:

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

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

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

    开启线程的两种方式:

    1.实例化Tread类,target参数用于指定子线程要执行的任务

    from threading import  Thread
    
    def task():
        print("子线程 run........")
    
    t = Thread(target=task)
    t.start()
    print("over")
    

    2.继承Tread类,覆盖run方法

    from threading import  Thread
    
    class MyThread(Thread):
        def run(self):
            print("子线程 run........")
    
    t = MyThread()
    t.start()
    print("over")
    

    与进程在使用方法上没有任何区别,不同的是开启子线程的代码可以写在任意位置

    之所以使用方法完全相同是因为,多进程其实是为了弥补多线程的缺憾而诞生的。详见GIL锁

    线程与进程区别:

    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()) # 是否为守护线程
    

    守护线程:

    设置守护线程的语法与进程相同,相同的是也必须放在线程开启前设置,否则抛出异常。

    守护线程的特点:

    ​ 守护线程会在被守护线程结束后立即结束

    from threading import  Thread
    import time
    
    def task():
        print("start......")
        time.sleep(5)
        print("end......")
    
    t = Thread(target=task)
    # t.setDaemon(True)
    t.daemon = True
    t.start()
    print("main over!")
    

    疑惑:

    from threading import  Thread
    import time
    
    def task():
        print("start....1")
        time.sleep(3)
        print("end......1")
    
    def task2():
        print("start....2")
        time.sleep(4)
        print("end......2")
    
    t = Thread(target=task)
    t.daemon = True
    t.start()
    
    t2 = Thread(target=task2)
    t2.start()
    
    print("main over!")
    

    打印main over后主线程代码执行完毕,但是守护线程t1并没有立即结束,这是什么原因呢?

    答:主线程会等待所有子线程执行完毕后结束

    ​ 在上述例子中,一共有三个线程,主线程 ,t1,t2

    ​ 虽然t1是守护线程 ,但是t2并不是所以主线程会等待t2执行结束才结束

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

    换句话说,守护线程会随着所有非守护线程结束而结束。

    线程锁

    互斥锁

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

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

    a = 10
    #lock = Lock()
    def task():
        global a
        #lock.acquire()
        b = a - 1
        time.sleep(0.1)
        a = b
        #lock.release()
    for i in  range(10):
        t = Thread(target=task)
        t.start()
    
    for t in threading.enumerate():
        if t != threading.current_thread():
            t.join()
    print(a)
    # 输出 9
    

    当多个线程要并发修改同一资源时,也需要加互斥锁来保证数据安全。

    同样的一旦加锁,就意味着串行,效率必然降低。

    死锁

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

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

    l1 = Lock()
    l2 = Lock()
    
    def task():
        l1.acquire()
        print(threading.current_thread().name,"拿到了筷子")
        time.sleep(0.1)
        l2.acquire()
        print(threading.current_thread().name, "拿到了盘子")
    
        print("吃饭")
        l1.release()
        l2.release()
    
    def task2():
        l2.acquire()
        print(threading.current_thread().name, "拿到了盘子")
    
        l1.acquire()
        print(threading.current_thread().name,"拿到了筷子")
    
        print("吃饭")
    
        l2.release()
        l1.release()
    
    t1 = Thread(target=task)
    t1.start()
    t2 = Thread(target=task2)
    t2.start()
    

    共有两把锁,但是一人拿到了一把,并且互不释放,相互等待,导致程序卡死,这就死锁。

    要发生死锁只有两种情况

    ​ 1.有不止一把锁,不同线程或进程分别拿到了不同的锁不放

    ​ 2.对同一把锁执行了多次acquire

    其中第二种情况我们可以通过可重入锁来解决

    可重入锁

    Rlock 同一个线程可以多次执行acquire,释放锁时,有几次acquire就要release几次。

    但是本质上同一个线程多次执行acquire时没有任何意义的,其他线程必须等到RLock全部release之后才能访问共享资源。

    所以Rlock仅仅是帮你解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题

    # 同一把RLock 多次acquire
    #l1 = RLock()
    #l2 = l1
    
    # 不同的RLock 依然会锁死
    #l1 = RLock()
    #l2 = RLock()
    
    def task():
        l1.acquire()
        print(threading.current_thread().name,"拿到了筷子")
        time.sleep(0.1)
        l2.acquire()
        print(threading.current_thread().name, "拿到了盘子")
    
        print("吃饭")
        l1.release()
        l2.release()
    
    def task2():
        l2.acquire()
        print(threading.current_thread().name, "拿到了盘子")
    
        l1.acquire()
        print(threading.current_thread().name,"拿到了筷子")
    
        print("吃饭")
    
        l2.release()
        l1.release()
    
    t1 = Thread(target=task)
    t1.start()
    t2 = Thread(target=task2)
    t2.start()
    

    忠告:在处理并发安全时 用完公共资源后一定要释放锁

    信号量

    Semaphore

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

    ​ 如果把Lock比喻为家用洗手间,同一时间只能一个人使用。

    ​ 那信号量就可以看做公共卫生间,同一时间可以有多个人同时使用。

    from threading import  Thread,Semaphore,current_thread
    import time
    
    s = Semaphore(3)
    def task():
        s.acquire()
        print("%s running........" % current_thread())
        time.sleep(1)
        s.release()
        
    for i in range(20):
        Thread(target=task).start()
    

  • 相关阅读:
    磁盘缓存
    算法与追MM(转)
    人人都能上清华(转)
    软件加密技术和注册机制原理攻略(转)
    计算二重定积分
    C++运算符重载
    STL中list的用法
    累了??放松一下,看几张关于程序员的几张搞笑图片
    解决来QQ消息后歌曲音量降低问题
    搞ACM的你伤不起(转)
  • 原文地址:https://www.cnblogs.com/yangyuanhu/p/11127149.html
Copyright © 2011-2022 走看看