0
点赞
收藏
分享

微信扫一扫

Python 多线程详解

ZMXQQ233 2022-01-20 阅读 118

多线程详解

什么是线程?

为什么要使用多线程?

总结起来,使用多线程编程具有如下几个优点

普通创建方式

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

自定义线程

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

守护线程

下面这个例子,这里使用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')

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

主线程等待子线程结束

为了让守护线程执行结束之后,主线程再结束,我们可以使用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')

多线程共享全局变量

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

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

互斥锁

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

递归锁

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

信号量(BoundedSemaphore类)

def run(n,semaphore):
    semaphore.acquire()   #加锁
    time.sleep(3)
    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
    else:
        print('----------all threads done-----------')
python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下的几个方法:
  • clear将flag设置为 False
  • set将flag设置为 True
  • is_set判断是否设置了flag
  • wait会一直监听flag,如果没有检测到flag就一直处于阻塞状态
  • 事件处理的机制:全局定义了一个Flag,
  • 当Flag的值为False,那么event.wait()就会阻塞,
  • 当Flag值为True, 那么event.wait()便不再阻塞
event = threading.Event()
def lighter():
    count = 0
    event.set()         #初始者为绿灯
    while True:
        if 5 < count <=10:
            event.clear()  #红灯,清除标志位
            print("\33[41;lmred light is on...\033[0m]")
        elif count > 10:
            event.set()    #绿灯,设置标志位
            count = 0
        else:
            print('\33[42;lmgreen 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)


# startTime = time.time()
light = threading.Thread(target=lighter,)
light.start()

car = threading.Thread(target=car,args=('MINT',))
car.start()
endTime = time.time()
# print('用时:',endTime-startTime)

GIL 全局解释器

python针对不同类型的代码执行效率也是不同的

  • 1、CPU密集型代码(各种循环处理、计算等),在这种情况下,由于计算工作多,ticks技术很快就会达到阀值,然后出发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。

  • 2、IO密集型代码(文件处理、网络爬虫等设计文件读写操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待, 造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序的执行效率)。所以python的多线程对IO密集型代码比较友好。

主要要看任务的类型,我们把任务分为I/O密集型和计算密集型,而多线程在切换中又分为I/O切换和时间切换。

  • 如果任务属于是I/O密集型,若不采用多线程,我们在进行I/O操作时,势必要等待前面一个I/O任务完成后面的I/O任务才能进行,在这个等待的过程中,CPU处于等待状态,这时如果采用多线程的话,刚好可以切换到进行另一个I/O任务。这样就刚好可以充分利用CPU避免CPU处于闲置状态,提高效率。

  • 如果多线程任务都是计算型,CPU会一直在进行工作,直到一定的时间后采取多线程时间切换的方式进行切换线程,此时CPU一直处于工作状态,此种情况下并不能提高性能,相反在切换多线程任务时,可能还会造成时间和资源的浪费,导致效能下降。这就是造成上面两种多线程结果不能的解释。

  • 结论:I/O密集型任务,建议采取多线程,还可以采用多进程+协程的方式(例如:爬虫多采用多线程处理爬取的数据);对于计算密集型任务,python此时就不适用了。

举报

相关推荐

0 条评论