Python提供了thread、threading等模块来进行线程的创建与管理,后者在线程管理能力上更进一步,因此我们通常使用threading模块。创建一个线程需要指定该线程执行的任务(函数名)、以及该函数需要的参数,示例代码如下所示:
1.通过实例化threading.Thread类来创建线程
import time
import threading
def get_thread_attributes(id): # 获取线程的属性
print('线程名称:%s,id=%d,开始时间:%s'%(threading.currentThread().name,id,time.strftime("%Y-%m-%d %H:%M:%S")))
time.sleep(id)
print('线程名称:%s,id=%d,结束时间:%s'%(threading.currentThread().name,id,time.strftime("%Y-%m-%d %H:%M:%S")))
# 定义线程
thread1 = threading.Thread(target=get_thread_attributes,args=(5,))
thread2 = threading.Thread(target=get_thread_attributes,args=(2,))
thread1.start()
thread2.start()
print('线程名称:%s,id=%d,结束时间:%s' % (threading.currentThread().name, 0, time.strftime("%Y-%m-%d %H:%M:%S")))
2.继承threading.Thread类,并重写__init__方法和run方法,当调用线程对象的start方法时,会自动调用run方法。
import time
import threading
class MyThread(threading.Thread):
def __init__(self,id):
super(MyThread,self).__init__()
self.id = id
def run(self):
print('线程名称:%s,id=%d,开始时间:%s' % (threading.currentThread().name, self.id, time.strftime("%Y-%m-%d %H:%M:%S")))
time.sleep(self.id)
print('线程名称:%s,id=%d,结束时间:%s' % (threading.currentThread().name, self.id, time.strftime("%Y-%m-%d %H:%M:%S")))
thread1 = MyThread(5)
thread2 = MyThread(3)
thread1.start()
thread2.start()
print('线程名称:%s,id=%d,结束时间:%s' % (threading.currentThread().name, 0, time.strftime("%Y-%m-%d %H:%M:%S")))
3.通过继承类threading.Thread构建子线程类,以及在run方法引入其他外部函数,同样可以达到上面的效果。
import time
import threading
def outer_task(id):
print('线程名称:%s,id=%d,开始时间:%s' % (threading.currentThread().name, id, time.strftime("%Y-%m-%d %H:%M:%S")))
time.sleep(id)
print('线程名称:%s,id=%d,结束时间:%s' % (threading.currentThread().name, id, time.strftime("%Y-%m-%d %H:%M:%S")))
class MyThread(threading.Thread):
def __init__(self,target,args):
super(MyThread,self).__init__()
self.target = target
self.args = args
def run(self):
self.target(*self.args)
thread1 = MyThread(outer_task,args=(5,))
thread2 = MyThread(outer_task,args=(3,))
thread1.start()
thread2.start()
print('线程名称:%s,id=%d,结束时间:%s' % (threading.currentThread().name, 0, time.strftime("%Y-%m-%d %H:%M:%S")))
线程是程序执行的最小单位,Python在进程启动起来后,会自动创建一个主线程,之后使用多线程机制可以在此基础上进行分支,产生新的子线程。子线程启动起来后,主线程默认会等待所有线程执行完成之后再退出。但是我们可以将子线程设置为守护线程,此时主线程任务一旦完成,所有子线程将会和主线程一起结束(就算子线程没有执行完也会退出)。
守护线程可以在线程启动之前,通过setDaemon(True)的形式进行设置,或者在创建子线程对象时,以参数的形式指定:
thread01 = Thread(target=target01, args="", name="线程1", daemon=True)
但是需要注意,如果希望主程序不等待任何线程直接退出,只有所有的线程都被设置为守护线程才有用。
我们可以用join()方法使主线程陷入阻塞,以等待某个线程执行完毕。因此这也是实现线程同步的一种方式。参数timeout 可以用来设置主线程陷入阻塞的时间,如果线程不是守护线程,即没有设置daemon为True,那么参数timeout 是无效的,主线程会一直阻塞,直到子线程执行结束。
import time
from threading import Thread, current_thread
def target():
if current_thread().name == "1":
time.sleep(5)
else:
time.sleep(6)
print("线程{}已退出".format(current_thread().name))
thread01 = Thread(target=target, daemon=True, name="1")
thread02 = Thread(target=target, daemon=True, name="2")
thread01.start()
thread02.start()
print("程序因线程1陷入阻塞")
thread01.join(timeout=3)
print("程序因线程2陷入阻塞")
thread02.join(timeout=3)
print("主线程已退出")
线程间通信方式有两种:共享内存和消息传递。 不同进程间线程通信等同于进程间通信,同一进程间可用共享内存实现。
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()。
可以通过threading.lock类来创建锁对象,一旦一个线程获得一个锁,会阻塞之后所有尝试获得该锁对象的线程,直到它被重新释放。这里举一个例子,通过加锁来确保两个线程在对同一个全局变量进行读写时的数据安全:
from threading import Thread, Lock
from time import sleep
book_num = 100 # 图书馆最开始有100本图书
bookLock = Lock()
def books_return():
global book_num
while True:
bookLock.acquire()
book_num += 1
print("归还1本,现有图书{}本".format(book_num))
bookLock.release()
sleep(1) # 模拟事件发生周期
def books_lease():
global book_num
while True:
bookLock.acquire()
book_num -= 1
print("借走1本,现有图书{}本".format(book_num))
bookLock.release()
sleep(2) # 模拟事件发生周期
if __name__ == "__main__":
thread_lease = Thread(target=books_lease)
thread_return = Thread(target=books_return)
thread_lease.start()
thread_return.start()
我们可以采用Python的queue模块来实现线程通信。Python中的queue 模块实现了多生产者、多消费者队列,特别适用于在多线程间安全的进行信息交换。该模块提供了4种我们可以利用的队列容器,分别 Queue(先进先出队列)、LifoQueue(先进后出队列)、PriortyQueue(优先级队列)、SimpleQueue(无界的先进先出队列,简单实现,缺少Queue中的任务跟踪等高级功能)。下面我们以Queue 为例介绍其使用方法,其他容器请自行查阅。
import queue
from random import choice
from threading import Thread
q = queue.Queue(maxsize=5)
dealList = ["红烧猪蹄", "卤鸡爪", "酸菜鱼", "糖醋里脊", "九转大肠", "阳春面", "烤鸭", "烧鸡", "剁椒鱼头", "酸汤肥牛", "炖羊肉"]
def cooking(chefname: str):
for i in range(4):
deal = choice(dealList)
q.put(deal, block=True)
print("厨师{}给大家带来一道:{} ".format(chefname, deal))
def eating(custname: str):
for i in range(3):
deal = q.get(block=True)
print("顾客{}吃掉了:{} ".format(custname, deal))
q.task_done()
if __name__ == "__main__":
# 创建并启动厨师ABC线程,创建并启动顾客1234线程
threadlist_chef = [Thread(target=cooking, args=chefname).start() for chefname in ["A", "B", "C"]]
threadlist_cust = [Thread(target=eating, args=str(custname)).start() for custname in range(4)]
# 队列阻塞,直到所有线程对每个元素都调用了task_done
q.join()