Skip to content

多线程

多线程介绍

线程是一个单独的执行流程,这意味着程序将同时发生两件事。但是对于大多数 Python3 实现,不同的线程实际上并不是同时执行的:它们只是看起来像。

顺序执行逻辑

在默认的情况下,代码的执行逻辑是从上到下,从左到右。并且一次只能做一件事情,即使是阻塞操作,也会停止不动。

以烧水泡茶为例,它需要做 7 项工作,即洗壶,灌凉水,烧水,洗茶杯,放茶叶,冲开水泡茶。

要完成这几项工作,可以有以下几种程序:

1.洗好开水壶,灌上凉水,放在火上,等待水开;水开后,再洗茶杯,准备茶叶,冲水泡茶。

2.先洗好水壶,洗好茶杯,放好茶叶,一切就绪,再放水烧水,水开后再冲水饮茶;

3.洗净开水壶,灌水烧水;烧水过程中,洗茶杯,放茶叶,水开后泡茶喝。

image-20201211145511532

顺序执行逻辑

python
import time

start_time = time.time()
print('1. 洗壶:1min')
time.sleep(1)
print('2. 灌凉水:1min')
time.sleep(1)
print('3. 烧水:1min')
time.sleep(1)
print('4. 等水烧开:3min')
time.sleep(1)
time.sleep(1)
time.sleep(1)
print('5. 洗茶杯:1min')
time.sleep(1)
print('6. 放茶叶:1min')
time.sleep(1)
print('7. 泡茶:1min')
time.sleep(1)
print('总运行时间', time.time() - start_time)

多线程异步执行

在默认的情况下,像等水烧开这种耗时的操作计算机也会在等着事情做完之后才会去做下一件事情,程序员可以指定给计算机安排任务。让计算机遇到耗时的事情之后,可以在等待的同时去做其他的事情。

python
import time
import threading


def work():
    """只有函数对象才能使用多线程"""
    print('5. 洗茶杯:1min')
    time.sleep(1)
    print('6. 放茶叶:1min')
    time.sleep(1)


start_time = time.time()

print('1. 洗壶:1min')
time.sleep(1)
print('2. 灌凉水:1min')
time.sleep(1)
print('3. 烧水:1min')
time.sleep(1)
print('4. 等水烧开:3min')

work_thread = threading.Thread(target=work)
work_thread.start()

time.sleep(1)  # 5. 洗茶杯:1min
time.sleep(1)  # 6. 放茶叶:1min
time.sleep(1)
# 5 6 需要请一个帮手帮我们去做
# print('5. 洗茶杯:1min')
# time.sleep(1)
# print('6. 放茶叶:1min')
# time.sleep(1)
# 多线程必须要是一个函数对象

print('7. 泡茶:1min')
time.sleep(1)

print('总共花了:', time.time() - start_time)

创建线程

现在您已经了解了线程是什么,让我们学习如何创建一个线程。Python标准库提供 threadingThread 在这个模块中,很好地封装了线程,提供了一个干净的界面来使用它们。

要启动一个单独的线程,您需要创建一个Thread实例,然后告诉它.start()

python
import time
import threading


def download():
    print("开始下载文件...")
    time.sleep(1)
    print("完成下载文件...")


def upload():
    print("开始上传文件...")
    time.sleep(1)
    print("完成上传文件...")


download()
upload()

注意

  • 很显然刚刚的程序并没有完成上传和下载同时进行的要求
  • 如果想要实现“上传与下载”同时进行,那么就需要一个新的方法,叫做:多任务

使用 threading 模块

python 的 thread 模块是底层的模块,python 的 threading 模块是对 thread 做了一些包装的,可以更加方便的被使用。

当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在 python 中,默认情况下(其实就是 setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。

python
import time
import threading


def download():
    print("开始下载文件...")
    time.sleep(1)
    print("完成下载文件...")


def upload():
    print("开始上传文件...")
    time.sleep(1)
    print("完成上传文件...")


download_thread = threading.Thread(target=download)
download_thread.start()
upload_thread = threading.Thread(target=upload)
upload_thread.start()

带参数的线程

python
# -*- coding: utf-8 -*-
import threading
import time

urls = [
    'https://maoyan.com/board/4?offset=0',
    'https://maoyan.com/board/4?offset=10',
    'https://maoyan.com/board/4?offset=20',
    'https://maoyan.com/board/4?offset=30',
    'https://maoyan.com/board/4?offset=40',
    'https://maoyan.com/board/4?offset=50',
    'https://maoyan.com/board/4?offset=60',
    'https://maoyan.com/board/4?offset=70',
    'https://maoyan.com/board/4?offset=80',
    'https://maoyan.com/board/4?offset=90',
]


def download(url):
    print('下载文件开始...')
    print(url)
    # 延时从操作
    time.sleep(1)
    print('下载文件完毕...')


if __name__ == '__main__':
    for url in urls:
        download(url=url)

参考答案

python
if __name__ == '__main__':
    for url in urls:
        # download(url=url)
        # target: 线程执行的函数名
        # args: 表示以元组的方式给函数传参
        # kwargs: 表示以字典的方式给函数传参
        t = threading.Thread(target=download, args=(url,))
        t.start()

案例-多线程 socket 服务器

利用上一节课学习的网络编程知识,实现一个多线程版本的 socket 服务器,同时给多个客户端提供服务。

多线程 socket 服务器

附录

多线流程图