Python多线程threading模块基本用法

本文介绍了Python多线程threading模块的基本用法。


系列文章

Python3 基础教程最全总结
Python3 进阶教程最全总结

一文掌握Python基础知识
一文掌握Python列表/元组/字典/集合
一文掌握Python函数用法
Python面向对象之类与对象详解
Python面向对象之装饰器与封装详解
Python面向对象之继承和多态详解
Python异常处理和模块详解
Python文件(I/O)操作详解

Python网络编程之Socket原理与基本用法
Python多线程threading模块基本用法

Python爬虫正则表达式详解 爬爬爬爬个虫子
Python爬虫实战Urllib抓取段子
Python爬虫实战抓包分析视频评论
Python爬虫实战Requests抓取博客文章
Python爬虫实战Scrapy抓取商品信息并写入数据库



多线程(threading模块)官方文档


1. 多线程

1.1 多线程简介

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

  • 可以把占据长时间的程序中的任务放到后台去处理;
  • 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出进度条来显示处理的进度;
  • 程序的运行速度可能加快(注意是“可能”);
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等情况下,可以释放一些珍贵的资源如内存占用等等。

1.2 线程模块

Python通过两个标准库thread和threading提供对线程的支持。threading提供了低级别的、原始的线程以及一个简单的锁。threading 模块提供的其他方法:

  • threading.currentThread():返回当前的线程变量。
  • threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
import threading
import pprint

def main():
    print('当前已经激活的线程数:%s \n'% threading.active_count())
    print('查看当前激活的线程:%s \n'% threading.enumerate())
    print('正在运行的线程:%s \n'% threading.current_thread())

if __name__ == '__main__':
    main()

输出:

当前已经激活的线程数:5 

查看当前激活的线程:[<_MainThread(MainThread, started 4612)>, <Thread(Thread-4, started daemon 5424)>, <Heartbeat(Thread-5, started daemon 6312)>, <HistorySavingThread(IPythonHistorySavingThread, started 17564)>, <ParentPollerWindows(Thread-3, started daemon 14200)>] 

正在运行的线程:<_MainThread(MainThread, started 4612)> 

threading模块中Thread类的其它方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。
import threading
import time

# 添加线程
def thread_1():
    print('This is the first added Thread, ID is %s \n' % threading.current_thread())
    current_time = time.strftime('%a %b %d %Y %H:%M:%S')
    print('thread_1 start...,start time:%s \n' % current_time)
    for i in range(5):
        time.sleep(1)
    print('thread_1 end,end time:%s \n' % current_time)

def thread_2():
    current_time = time.strftime('%a %b %d %Y %H:%M:%S')
    print('thread_2 start...,start time:%s \n' % current_time)
    print('This is the second added Thread, ID is %s \n' % threading.current_thread())
    print('thread_2 end,end time:%s \n' % current_time)

def main():
    # 添加线程
    added_thread_1 = threading.Thread(target = thread_1, name = 'T1')
    added_thread_2 = threading.Thread(target = thread_2, name = 'T2')
    # 启动线程
    added_thread_1.start()
    added_thread_2.start()
    # join方法
    #added_thread_1.join()
    #added_thread_2.join()
    print('All done! \n')

if __name__ == '__main__':
    main()

输出:

This is the first added Thread, ID is <Thread(T1, started 18256)> 

thread_1 start...,start time:Fri Apr 03 2020 18:35:07 

thread_2 start...,start time:Fri Apr 03 2020 18:35:07 
All done! 


This is the second added Thread, ID is <Thread(T2, started 2404)> 

thread_2 end,end time:Fri Apr 03 2020 18:35:07 

thread_1 end,end time:Fri Apr 03 2020 18:35:07 

由输出信息可知,多线程是同时进行的,如果想实现所有线程运行完毕,再执行主函数中的其他语句,可以使用 join() 方法。使用 join() 方法的输出:

This is the first added Thread, ID is <Thread(T1, started 9428)> 

thread_1 start...,start time:Fri Apr 03 2020 14:49:46 

thread_2 start...,start time:Fri Apr 03 2020 14:49:46 

This is the second added Thread, ID is <Thread(T2, started 14592)> 

thread_2 end,end time:Fri Apr 03 2020 14:49:46 

thread_1 end,end time:Fri Apr 03 2020 14:49:46 

All done! 

可以看出,主函数中的其它语句在等待其他线程执行完毕之后才会运行。


1.3 Queue()

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

Queue模块中的常用方法:

  • Queue.qsize() 返回队列的大小;
  • Queue.empty() 如果队列为空,返回True,反之False;
  • Queue.full() 如果队列满了,返回True,反之False;
  • Queue.full 与 maxsize 大小对应;
  • Queue.get([block[, timeout]]) 获取队列,timeout等待时间;
  • Queue.get_nowait() 相当Queue.get(False);
  • Queue.put(item)写入队列,timeout等待时间;
  • Queue.put_nowait(item) 相当 Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号;
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作;
#Queue 实现有返回值操作
import threading
import time
import queue 
import numpy as np

def job(list_input, q):
    for i in range(len(list_input)):
        list_input[i] = list_input[i] ** 2
    #return list_input
    q.put(list_input)

def multi_threading(data):
    q = queue.Queue()
    threads = []

    for i in range(4):
        t = threading.Thread(target = job, 
                             args = (data[i], q))
        
        t.start()#启动线程        
        threads.append(t) #添加到线程列表
    
    for thread in threads:
        thread.join() # 将线程列表中的所有线程添加到主线程
    
    results = []
    
    for i_ in range(4):
        #q.get() #Queue() 按照顺序每次单个取值
        results.append(q.get())
    print(results)
    
data = np.random.randint(1, 20, size=(4,3))  
multi_threading(data)

输出:

[array([225, 289,   4]), array([225, 144,  16]), array([324,  81,   9]), array([ 49,  64, 196])]

1.4 GIL

GIL全称 Global Interpreter Lock,为全局解释器锁。GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。CPython 不会让一个线程一直独占解释器,它会轮流执行 Python 线程。这样一来,用户看到的就是“伪”并行,即 Python 线程在交替执行,来模拟真正并行的线程。 GIL 限制了 Python 多线程的性能不会像我们预期的那样,即多线程的运行时间不降反增。关于原理,可以访问文末最后两篇文章,这里不做过多介绍,只需知道:Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。Python GIL底层实现原理图如下:
Python多线程threading模块基本用法_第1张图片
Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。


import threading
from queue import Queue
import copy
import time

def job(list_input, q):
    res = sum(list_input)
    q.put(res)


def multi_threading(list_input):
    q = Queue()
    threads = []
    for i in range(4):
        t = threading.Thread(target=job, args=(copy.copy(list_input), q), name='T%i' % i)
        t.start()
        threads.append(t)
    [t.join() for t in threads]
    total = 0
    for _ in range(4):
        total += q.get()
    print(total)

def normal(ist_input):
    total = sum(ist_input)
    print(total)

if __name__ == '__main__':
    ist_input = list(range(1000000))
    s_t = time.time()
    normal(ist_input*4)
    print('normal: ',time.time()-s_t)
    s_t = time.time()
    multithreading(ist_input)
    print('multithreading: ', time.time()-s_t)

输出:

1999998000000
normal:  0.1658337116241455
1999998000000
multithreading:  0.16408824920654297

1.5 线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有 acquire() 方法和release() 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire()release() 方法之间。

#lock
import threading

def job_1():
    global A, lock
    lock.acquire()
    for i in range(10):
        A += 1
        print('job_1', A)
    lock.release()

def job_2():
    global A, lock
    lock.acquire()
    for i in range(10):
        A += 10
        print('job_2', A)
    lock.release()

if __name__ == '__main__':
    lock = threading.Lock()
    A = 0
    t1 = threading.Thread(target=job_1)
    t2 = threading.Thread(target=job_2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

参考:
threading官方文档:https://docs.python.org/3.7/library/threading.html?highlight=threading#module-threading
Python time模块:https://www.runoob.com/python/python-date-time.html
queue官方文档:https://docs.python.org/3.7/library/queue.html?highlight=queue#module-queue
https://edu.aliyun.com/lesson_505_5754?spm=5176.10731542.0.0.d9ba25aeDtbi05#_5754
https://www.bilibili.com/video/BV1jW411Y7Wj
http://c.biancheng.net/view/5537.html
http://cenalulu.github.io/python/gil-in-python/

你可能感兴趣的:(Python)