Python网络与并发编程 09 threading模块使用

threading模块

Python中提供了threading模块来实现线程并发编程,官方文档如下:

官方文档

添加子线程

实例化Thread类

使用该方式新增子线程任务是比较常见的,也是推荐使用的。

简单的代码示例如下,创建3个子线程并向其添加任务,然后运行并打印它们的线程ID和线程名字:

import threading
import time
def task(params):
    print("sub thread run")
    currentThread = threading.current_thread()
    time.sleep(3)
    print("current subthread id : %s\ncurrent subthread name : %s\ncurrent subthread params : %s" % (
        currentThread.ident, currentThread.name, params))
if __name__ == "__main__":
    print("main thread run")
    for item in range(3):
        subThreadIns = threading.Thread(target=task, args=(item, ))
        subThreadIns.start()
    print("main thread run end")

# main thread run
# sub thread run
# sub thread run
# sub thread run
# main thread run end
# current subthread id : 123145534398464
# current subthread name : Thread-1
# current subthread params : 0
# current subthread id : 123145544908800
# current subthread name : Thread-3
# current subthread params : 2
# current subthread id : 123145539653632
# current subthread name : Thread-2
# current subthread params : 1

*:返回一个线程对象,注意args的参数必须是一个tuple,否则抛出异常,也就是说单实参必须添加逗号

*:start()方法是指该线程对象能够被系统调度了,但不是立即运行该线程,而是等待系统调度后才运行。所以你会看见上面子线程的运行顺序是0、2、1,另外一个线程对象只能运行一次该方法,若多次运行则抛出RunTimeError的异常。

*:获取当前的线程对象

*:获取当前线程对象的编号和名字,以及传入的参数。当线程启动时,系统都会分配给它一个随机的编号和名字

首先上述代码会先运行主线程,然后会创建3个子线程并运行。

当子线程运行的时候碰到了sleep(3)这种I/O操作时会释放掉GIL锁,并将线程执行权交还给了主线程。

然后主线程就运行完毕了,此时主线程并不会被kill掉,而是等待子线程运行结束后才会被kill掉,而子线程则是运行完毕后会被立刻kill掉。

我们可以看见,上面3个任务如果按照串行执行共会花费9.+秒时间,而通过多线程来运行,则仅需花费3.+秒的时间,极大的提升了任务处理效率。

自定义类覆写run()方法

上面的子线程任务对象是一个全局函数,我们也可以将它作为方法来进行调用。

书写一个类并继承Threading类,覆写run()方法即可:

import threading
import time
class TaskClass(threading.Thread):  # *
    def __init__(self, params):
        self.params = params  # *
        super(__class__, self).__init__()

    def run(self):
        print("sub thread run")
        currentThread = threading.currentThread()
        time.sleep(3)
        print("current subthread id : %s\ncurrent subthread name : %s\ncurrent subthread params : %s" % (
            currentThread.ident, currentThread.name, self.params))
if __name__ == "__main__":
    print("main thread run")
    for item in range(3):
        subThreadIns = TaskClass(item)
        subThreadIns.start()
    print("main thread run end")

# main thread run
# sub thread run
# sub thread run
# sub thread run
# main thread run end
# current subthread id : 123145495068672
# current subthread name : Thread-1
# current subthread params : 0
# current subthread id : 123145500323840
# current subthread name : Thread-2
# current subthread params : 1
# current subthread id : 123145505579008
# current subthread name : Thread-3
# current subthread params : 2

*:必须继承Threading类并调用父类的__init__()方法

*:传入的参数

源码浅析

为什么添加子线程有2种截然不同的方式呢?它们之间有什么区别?这些都可以从源码中找到答案。

我们从Thread类的实例看起,首先是__init__()方法(threading.py line 738 - 800),它主要做了一些初始化的准备工作*