• Python-多线程详解


    一、介绍线程

    1)什么是线程?

    线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程

    2)为什么使用线程

     线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄 和其他进程应有的状态。 因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享 内存,从而极大的提升了程序的运行效率。 线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境 包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。 操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程 来实现并发比使用多进程的性能高得要多。

    3)线程的优点

     总结起来,使用多线程编程具有如下几个优点: 进程之间不能共享内存,但线程之间共享内存非常容易。 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高 python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。

    二、实现方式

    导包

    import threading
    import time
    from threading import Lock, Thread

    1)普通创建多线程方式

    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',))     # target是要执行的函数名(不是函数),args是函数对应的参数,以元组的形式存在
        t2 = threading.Thread(target=run,args=('t2',))
        t1.start()
        t2.start()

    2)自定义线程

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

    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')
        t2 = MyThread('t2')
        t1.start()
        t2.start()

    3)守护线程

    下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程, 因此当主线程结束后,子线程也会随之结束,所以当主线程结束后,整个程序就退出了。 所谓’线程守护’,就是主线程不管该线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。也就是说:主线程不等待该守护线程的执行完再去关闭。

    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)
        t.start()
        print('end')

    注:通过执行结果可以看出,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行

    4)主线程等待子线程结束

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

    def run(n):
        print('task',n)
        time.sleep(2)
        print('5s')
        time.sleep(2)
        print('3s')
        time.sleep(2)
        print('1s')
    if __name__ == '__main__':
        t=threading.Thread(target=run,args=('t1',))
        t.setDaemon(True)    #把子线程设置为守护线程,必须在start()之前设置
        t.start()
        t.join()     #设置主线程等待子线程结束
        print('end')

     5)多线程共享全局变量

    线程时进程的执行单元,进程时系统分配资源的最小执行单位,所以在同一个进程中的多线程是共享资源的

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

     6)互斥锁

    由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据, 所以出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁,像下面的代码,当需要独占 某一个资源时,任何一个锁都可以锁定这个资源,就好比你用不同的锁都可以把这个相同的门锁住一样。 由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期, 我们因此也称为“线程不安全”。

    def work():
        global n
        lock.acquire()      # 加锁
        temp = n
        time.sleep(0.1)
        n = temp-1
        print n
        lock.release()      # 解锁
    
    
    if __name__ == '__main__':
        lock = Lock()
        n = 100
        l = []
        # 创建 100 个线程
        for i in range(100):
            p = Thread(target=work)
            l.append(p)
            p.start()
        for p in l:
            p.join()

    7)递归锁

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

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

    8)信号量(BoundedSemaphore类)

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

    def run(n,semaphore):
        semaphore.acquire()   #加锁
        time.sleep(3)
        print('run the thread:%s
    ' % 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
        else:
            print('----------all threads done-----------')
  • 相关阅读:
    设计模式之适配器模式温故知新(九)
    设计模式之策略模式总结(八)
    设计模式之观察者模式, 个人感觉相当的重要(七)
    设计模式之抽象工厂模式读后(六)
    设计模式之工厂模式详细读后感TT!(五)
    设计模式之简单工厂模式, 加速(四)
    设计模式之代理模式笔记(三)
    设计模式之单例模式读后思考(二)
    为什么要用设计模式?先看看6大原则(一)
    Codeforces_835
  • 原文地址:https://www.cnblogs.com/shangwei/p/14304282.html
Copyright © 2020-2023  润新知