线程模块建立在线程的底层特性之上,使线程的工作变得更简单、更像python。使用线程允许程序在同一进程空间中并发运行多个操作。
很多人学习python,不知道从何学起。
很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。
很多已经做案例的人,却不知道如何去学习更加高深的知识。
那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!
QQ群:961562169
线程对象
使用线程最简单的方法是用目标函数实例化它,然后调用start()让它开始工作。
import threading
def worker():
"""线程worker函数"""
print('Worker')
return
threads = []
for i in range(5):
t = threading.Thread(target=worker)
threads.append(t)
t.start()
结果:输出为五行,每行上都有“Worker”
$ python3 threading_simple.py
Worker
Worker
Worker
Worker
Worker
能够生成一个线程并传递参数来告诉它要做什么工作是很有用的。这个例子传递一个数字,然后线程打印这个数字。
import threading
def worker(num):
"""线程worker函数"""
print('Worker: {num}' )
return
threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
现在每个线程打印的消息包含一个数字:
$ python3 -u threading_simpleargs.py
Worker: 0
Worker: 1
Worker: 2
Worker: 3
Worker: 4
确定当前线程
使用参数来标识或命名线程很麻烦,而且没有必要。每个Thread实例都有一个具有默认值的名称,该名字可以在创建线程时更改。命名线程在具有多个服务线程来处理不同操作的服务器进程中很有用。
import threading
import time
def worker():
print(threading.currentThread().getName(), '开始运行')
time.sleep(2)
print(threading.currentThread().getName(), '结束运行')
def my_service():
print(threading.currentThread().getName(), '开始运行')
time.sleep(3)
print(threading.currentThread().getName(), '结束运行')
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # 使用默认名字
w.start()
w2.start()
t.start()
调试输出包括每行上当前线程的名称。“线程名称”列中带有“Thread-1”的行对应于未命名的线程w2。
$ python -u threading_names.py
worker Thread-1 开始运行
my_service 开始运行
开始运行
Thread-1worker 结束运行
结束运行
my_service 结束运行
大多数程序不使用打印进行调试。logging支持使用格式化程序代码%(threadName)在每个日志消息中嵌入线程名称。在日志消息中包含线程名称可以更容易地将这些消息追溯到其源。
import logging
import threading
import time
logging.basicConfig(level=logging.DEBUG,
format='[%(levelname)s] (%(threadName)-10s) %(message)s',
)
def worker():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
def my_service():
logging.debug('开始运行')
time.sleep(3)
logging.debug('结束运行')
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # 使用默认名字
w.start()
w2.start()
t.start()
logging是线程安全的,因此来自不同线程的消息在输出中保持不同。
$ python threading_names_log.py
[DEBUG] (worker ) 开始运行
[DEBUG] (Thread-1 ) 开始运行
[DEBUG] (my_service) 开始运行
[DEBUG] (worker ) 结束运行
[DEBUG] (Thread-1 ) 结束运行
[DEBUG] (my_service) 结束运行
守护程序与非守护程序线程
到目前为止,示例程序已隐式地等待退出,直到所有线程都完成了它们的工作。有时程序生成一个线程作为守护进程运行,而该线程在运行时不会阻止主程序退出。使用守护进程线程对于那些可能无法轻松中断线程或让线程在其工作过程中死亡不会丢失或损坏数据的服务(例如,为服务监视工具生成“心跳”的线程)非常有用。要将线程标记为守护程序,请使用布尔参数调用其setDaemon()方法。默认情况下,线程不是守护程序,因此传递True将打开守护程序模式。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('开始运行')
logging.debug('结束运行')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
请注意,输出不包括来自守护进程线程的“结束运行”消息,因为所有非守护进程线程(包括主线程)都在守护进程线程从其两秒钟的睡眠中唤醒之前退出。
$ python threading_daemon.py
(daemon ) 开始运行
(non-daemon) 开始运行
(non-daemon) 结束运行
要等到守护进程线程完成其工作,请使用join()方法。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('开始运行')
logging.debug('结束运行')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join()
t.join()
等待守护线程使用join()退出意味着它有机会生成“结束运行”消息。
$ python threading_daemon_join.py
(daemon ) 开始运行
(non-daemon) 开始运行
(non-daemon) 结束运行
(daemon ) 结束运行
默认情况下,join()无限期阻塞。也可以传递一个超时参数(一个浮点数,表示等待线程变为非活动状态的秒数)。如果线程没有在超时时间内完成,join()仍然返回。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('开始运行')
logging.debug('结束运行')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join(1)
# python2写法
print ('d.isAlive()', d.isAlive())
# python3写法
print ('d.is_alive()', d.is_alive())
t.join()
由于传递的超时值小于守护进程线程的睡眠时间,因此join()返回后,线程仍然是“活动的”。
$ python threading_daemon_join_timeout.py
(daemon ) 开始运行
(non-daemon) 开始运行
(non-daemon) 结束运行
d.isAlive() True
d.is_alive() True
枚举所有线程
没有必要保留所有守护进程线程的显式句柄,以确保它们在退出主进程之前已完成。enumerate()返回活动线程实例的列表。该列表包含当前线程,并且由于不允许加入当前线程(这会导致死锁情况),因此必须跳过它。
import random
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def worker():
"""线程worker函数"""
t = threading.currentThread()
pause = random.randint(1,5)
logging.debug('sleeping %s', pause)
time.sleep(pause)
logging.debug('ending')
return
for i in range(3):
t = threading.Thread(target=worker)
t.setDaemon(True)
t.start()
main_thread = threading.currentThread()
for t in threading.enumerate():
if t is main_thread:
continue
logging.debug(f'joining { t.getName()}')
t.join()
由于worker睡眠的时间是随机的,所以这个程序的输出可能会有所不同。应该是这样的:
$ python threading_enumerate.py
(Thread-1 ) sleeping 3
(Thread-2 ) sleeping 2
(Thread-3 ) sleeping 5
(MainThread) joining Thread-1
(Thread-2 ) ending
(Thread-1 ) ending
(MainThread) joining Thread-3
(Thread-3 ) ending
(MainThread) joining Thread-
子类化线程
在启动时,线程进行一些基本的初始化,然后调用其run()方法,该方法调用传递给构造函数的目标函数。若要创建Thread的子类,请重写run()以执行任何必要的操作。
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
class MyThread(threading.Thread):
def run(self):
logging.debug('running')
return
for i in range(5):
t = MyThread()
t.start()
忽略run()的返回值。
$ python threading_subclass.py
(Thread-1 ) running
(Thread-2 ) running
(Thread-3 ) running
(Thread-4 ) running
(Thread-5 ) running
因为传递给Thread构造函数的args和kwargs值保存在私有变量中,因此不容易从子类访问它们。要将参数传递给自定义线程类型,请重新定义构造函数以将值保存在实例属性中,该属性可以在子类中看到。
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
class MyThreadWithArgs(threading.Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, verbose=None):
threading.Thread.__init__(self, group=group, target=target, name=name,
verbose=verbose)
self.args = args
self.kwargs = kwargs
return
def run(self):
logging.debug('running with %s and %s', self.args, self.kwargs)
return
for i in range(5):
t = MyThreadWithArgs(args=(i,), kwargs={'a':'A', 'b':'B'})
t.start()
MyThreadWithArgs与Thread使用相同的API,但是与其他任何类一样,另一个类可以轻松地更改构造函数方法以采用与线程目的更直接相关的更多或不同的参数。
$ python threading_subclass_args.py
(Thread-1 ) running with (0,) and {'a': 'A', 'b': 'B'}
(Thread-2 ) running with (1,) and {'a': 'A', 'b': 'B'}
(Thread-3 ) running with (2,) and {'a': 'A', 'b': 'B'}
(Thread-4 ) running with (3,) and {'a': 'A', 'b': 'B'}
(Thread-5 ) running with (4,) and {'a': 'A', 'b': 'B'}
计时器线程
Timer提供了一个将Thread子类化的原因的示例,它也包含在线程中。计时器在延迟之后开始工作,并且可以在该延迟时间段内的任何时间取消。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def delayed():
logging.debug('worker running')
return
t1 = threading.Timer(3, delayed)
t1.setName('t1')
t2 = threading.Timer(3, delayed)
t2.setName('t2')
logging.debug('starting timers')
t1.start()
t2.start()
logging.debug('waiting before canceling %s', t2.getName())
time.sleep(2)
logging.debug('canceling %s', t2.getName())
t2.cancel()
logging.debug('done')
请注意,第二个计时器从不运行,而第一个计时器似乎在主程序的其余部分完成后运行。因为它不是守护进程线程,所以当主线程完成时,它是隐式连接的。
$ python threading_timer.py
(MainThread) starting timers
(MainThread) waiting before canceling t2
(MainThread) canceling t2
(MainThread) done
(t1 ) worker running
线程间信令
虽然使用多个线程的目的是分离出单独的操作以并发运行,但有时能够在两个或多个线程中同步这些操作是很重要的。线程之间通信的一个简单方法是使用事件对象。事件管理内部标志,调用者可以set()或clear()。其他线程可以wait()设置set(),有效地阻止进程,直到允许继续。
import logging
import threading
import time
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def wait_for_event(e):
"""Wait for the event to be set before doing anything"""
logging.debug('wait_for_event starting')
event_is_set = e.wait()
logging.debug('event set: %s', event_is_set)
def wait_for_event_timeout(e, t):
"""Wait t seconds and then timeout"""
while not e.isSet():
logging.debug('wait_for_event_timeout starting')
event_is_set = e.wait(t)
logging.debug('event set: %s', event_is_set)
if event_is_set:
logging.debug('processing event')
else:
logging.debug('doing other work')
e = threading.Event()
t1 = threading.Thread(name='block',
target=wait_for_event,
args=(e,))
t1.start()
t2 = threading.Thread(name='non-block',
target=wait_for_event_timeout,
args=(e, 2))
t2.start()
logging.debug('Waiting before calling Event.set()')
time.sleep(3)
e.set()
logging.debug('Event is set')
wait()方法在等待表示时间的参数之前占用了秒数。它返回一个布尔值,指示是否设置了事件,因此调用者知道wait()返回的原因。isSet()方法可以单独用于事件,而不必担心阻塞。
在本例中,wait_for_event_timeout()检查事件状态,不会无限期阻塞。wait_for_event()会阻塞对wait()的调用,直到事件状态更改后才会返回。
$ python threading_event.py
(block ) wait_for_event starting
(non-block ) wait_for_event_timeout starting
(MainThread) Waiting before calling Event.set()
(non-block ) event set: False
(non-block ) doing other work
(non-block ) wait_for_event_timeout starting
(MainThread) Event is set
(block ) event set: True
(non-block ) event set: True
(non-block ) processing event