• 关于python多线程的一些理解


    最近工作项目的原因,需要用到多线程,趁此机会,学习了下进程和线程的概念、用法及作用。

    python多线程的概念和作用

    关于python多线程的概念和作用,有下面一段解释:
    线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

    • 多线程类似于同时执行多个不同程序,具有如下优点:
    • 使用线程可以把占据长时间的程序中的任务放到后台去处理。
    • 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
    • 程序的运行速度可能加快
    • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

    上面的解释略微官方,看到知乎上一位大佬的解释,豁然开朗:

    1.单进程单线程:一个人在一个桌子上吃菜。
    2.单进程多线程:多个人在同一个桌子上一起吃菜。
    3.多进程单线程:多个人每个人在自己的桌子上吃菜。

    多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走了。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢

    1. 对于 Windows 系统来说,【开桌子】的开销(这里指CPU运行程序所花费时间)很大,因此 Windows 鼓励大家在一个桌子上吃菜。因此 Windows多线程学习重点是要大量面对资源争抢与同步方面的问题
    2. 对于 Linux 系统来说,【开桌子】的开销很小,因此 Linux鼓励大家尽量每个人都开自己的桌子吃菜。这带来新的问题是:坐在两张不同的桌子上,说话不方便。因此,Linux下的学习重点大家要学习进程间通讯的方法。

    通俗点讲,就是一个大程序里有多个子事件同时进行,为了不发生抢占和内存资源争抢,就需要人为安排线程,供指定子事件进行。

    python多线程的简单使用:threading模块

    一个简单的多线程实例:

    import threading
    import time
    
    def run(n):
        print("task", n)
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
        time.sleep(1)
        print('0s')
        time.sleep(1)
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=run, args=("t1",))
        t2 = threading.Thread(target=run, args=("t2",))
        t1.start()
        t2.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行结果:

    >>> task t1
    >>> task t2
    >>> 2s
    >>> 2s
    >>> 1s
    >>> 1s
    >>> 0s
    >>> 0s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里使用**threading.Thread(func,args)**方法创建两个线程,入参func是要给予线程资源的函数,args指线程的命名。

    自定义线程

    继承threading.Thread来自定义线程类,其本质是重构Thread类中的run方法:

    import threading
    import time
    
    class MyThread(threading.Thread):
        def __init__(self, n):
            super(MyThread, self).__init__()  # 重构run函数必须要写
            self.n = n
    
        def run(self):
            print("task", self.n)
            time.sleep(1)
            print('2s')
            time.sleep(1)
            print('1s')
            time.sleep(1)
            print('0s')
            time.sleep(1)
    
    if __name__ == "__main__":
        t1 = MyThread("t1")
        t1.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行结果:

    >>> task t1
    >>> 2s
    >>> 1s
    >>> 0s
    
    • 1
    • 2
    • 3
    • 4

    守护线程

    所有定义的子线程外,还有系统自动分配的主线程。使用**setDaemon(True)**把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。即主线程结束后,整个程序退出。

    import threading
    import time
    
    def run(n):
        print("task", n)
        time.sleep(1)       #此时子线程停1s
        print('3s')
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
    
    if __name__ == '__main__':
        t = threading.Thread(target=run, args=("t1",))
        t.setDaemon(True)   #把子进程设置为守护线程,必须在start()之前设置
        t.start()
        print("end")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:

    >>> task t1
    >>> end
    
    • 1
    • 2

    这里子线程还未打印出3s,2s,1s,就随着主线程打印end结束了。

    主线程等待子线程结束

    为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待子线程执行:

    import threading
    import time
    
    def run(n):
        print("task", n)
        time.sleep(1)    
        print('3s')
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
    
    if __name__ == '__main__':
        t = threading.Thread(target=run, args=("t1",))
        t.setDaemon(True)   #把子进程设置为守护线程,必须在start()之前设置
        t.start()
        t.join()            # 设置主线程等待子线程结束
        print("end")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行结果:

    >>> task t1
    >>> 3s
    >>> 2s
    >>> 1s
    >>> end
    
    • 1
    • 2
    • 3
    • 4
    • 5

    多线程共享全局变量

    线程是进程的执行单元,进程是系统分配资源的最小单位,(如同拉煤需要一列列火车,进程是一列火车,线程是一节节火车车厢)所以在同一个进程中的多线程是共享资源的。

    import threading
    import time
    
    g_num = 100
    
    def work1():
        global g_num
        for i in range(3):
            g_num += 1
        print("in work1 g_num is : %d" % g_num)
    
    def work2():
        global g_num
        print("in work2 g_num is : %d" % g_num)
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=work1)
        t1.start()
        time.sleep(1)
        t2 = threading.Thread(target=work2)
        t2.start()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果:

    >>> in work1 g_num is : 103
    >>> in work2 g_num is : 103
    
    • 1
    • 2

    互斥锁

    由于线程之间是进行随机调度,当多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。同时修改同一条数据时可能会出现所谓“脏数据”,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。
    为了方式上面情况的发生,就出现了互斥锁(Lock):

    from threading import Thread,Lock
    import os,time
    
    def work():
        global n
        lock.acquire()
        temp=n
        time.sleep(0.1)
        n=temp-1
        lock.release()
        
    if __name__ == '__main__':
        lock=Lock()
        n=100
        l=[]
        for i in range(100):
            p=Thread(target=work)
            l.append(p)
            p.start()
        for p in l:
            p.join()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    递归锁

    RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLcok类。

    import threading
    import time
    
    def Func(lock):
        global gl_num
        lock.acquire()
        gl_num += 1
        time.sleep(1)
        print(gl_num)
        lock.release()
    
    if __name__ == '__main__':
        gl_num = 0
        lock = threading.RLock()
        for i in range(10):
            t = threading.Thread(target=Func, args=(lock,))
            t.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    信号量

    互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。使用BoundedSemaphore类

    import threading
    import time
    
    def run(n, semaphore):
        semaphore.acquire()   #加锁
        time.sleep(1)
        print("run the thread:%s\n" % n)
        semaphore.release()     #释放
    
    if __name__ == '__main__':
        num = 0
        semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
        for i in range(22):
            t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
            t.start()
        while threading.active_count() != 1:
            pass  # print threading.active_count()
        else:
            print('-----all threads done-----')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    事件

    python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下Event类的几个方法:

    • clear 将flag设置为“False”
    • set 将flag设置为“True”
    • is_set 判断是否设置了flag
    • wait 会一直监听flag,如果没有检测到flag就一直处于阻塞状态

    事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。下面利用Event类模拟一个红绿灯的功能:

    import threading
    import time
    
    event = threading.Event()
    
    
    def lighter():
        count = 0
        event.set()     #初始值为绿灯
        while True:
            if 5 < count <=10 :
                event.clear()  # 红灯,清除标志位
                print("\33[41;1mred light is on...\033[0m")
            elif count > 10:
                event.set()  # 绿灯,设置标志位
                count = 0
            else:
                print("\33[42;1mgreen light is on...\033[0m")
    
            time.sleep(1)
            count += 1
    
    def car(name):
        while True:
            if event.is_set():      #判断是否设置了标志位
                print("[%s] running..."%name)
                time.sleep(1)
            else:
                print("[%s] sees red light,waiting..."%name)
                event.wait()
                print("[%s] green light is on,start going..."%name)
    
    light = threading.Thread(target=lighter,)
    light.start()
    
    car = threading.Thread(target=car,args=("MINI",))
    car.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    MySQL学习(三)——多表连接查询
    【UniApp】-uni-app-内置组件
    Codeforces Round #797 (Div. 3) E. Price Maximization
    【教程】uni-app iOS打包解决profile文件与私钥证书不匹配问题
    【youcans 的 OpenCV 例程200篇】180.基于距离变换的分水岭算法
    mysql 死锁详细分析(三)
    java华夏球迷俱乐部网站设计与实现计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    复星杏脉算法面经2024年5月16日面试
    el-form-item validator 携带额外的参数
    Python 中如何编写类型提示
  • 原文地址:https://blog.csdn.net/ningqingzy/article/details/126164757