今天简单测试了下python中的多线程与协程,之前在看python的教程中有说到python的多线程其实是在一个线程中来回切换并不会真正的去使用多核资源。如果想真正去利用多核资源,最好的办法是使用python的多进程+协程的方式(协程是一种充分利用单核资源的方法)。注意:并不是说协程一定比多线程效率高,这需要看情况,如果使用的是不支持协程异步的第三包也会慢的和单线程一样。下面我对不使用线程,使用线程以及使用协程进行了简单的性能测试。

下面写了一个函数fun1,该函数的功能就是暂停1秒后返回,如果不使用线程,用简单的循环去执行1000次,很明显大概耗时为1000s。

如果使用多线程去调用该函数fun1,循环1000次大概1.13s。

如果使用协程去调用函数fun2(fun2和fun1功能相同,只不过改成了适用协程的形式,注意fun2中的sleep不能用time的sleep),循环1000次大概1.02秒。

在本次测试实例中,python中多线程和协程的速度差不多。具体使用什么方法,根据应用场景来定。如果将循环次数改成100000次,用多线程耗时16.97s,协程使用2.89s。在并发要求不高的情况下,一般多线程比协程效果更好,如果要求高并发协程的效果比多线程更好。还要注意一点,如果在你调用的函数或者第三方包不支持协程,比如存在time.sleep()函数,建议使用多线程,否则协程的速度可能和单线程的速度一样慢了。

import time
import threading
import asyncio


def fun1(n):
    time.sleep(1)
    print("{} is done!".format(n))


# 不使用线程  大概1000s
t1 = time.perf_counter()
for i in range(1000):
    fun1(i)
t2 = time.perf_counter()
print(t2 - t1)

# 使用多线程 1.13s
f = []
t1 = time.perf_counter()
for i in range(1000):
    f.append(threading.Thread(target=fun1, args=(i,)))
    f[i].start()
for i in range(1000):
    f[i].join()
t2 = time.perf_counter()
print(t2 - t1)


async def fun2(n):
    # 在真实使用场景中,这里的asyncio.sleep替换成支持协程的方法
    await asyncio.sleep(1)
    print("{} is done!".format(n))


# 使用协程 1.02s
t1 = time.perf_counter()
loop = asyncio.get_event_loop()
tasks = []
for i in range(1000):
    tasks.append(fun2(i))
loop.run_until_complete((asyncio.wait(tasks)))
t2 = time.perf_counter()
print(t2 - t1)

如果需要获得多线程的输出,以下提供一种方法:

自己继承多线程类写一个能够获取输出的多线程类,然后通过自己的定义的多线程类去开启多线程。

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, func, args=()):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args
        self.result = None

    def run(self):
        self.result = self.func(*self.args)

    def get_result(self):
        try:
            return self.result
        except Exception:
            return None


def my_fun(n):
    time.sleep(1)
    return "{} is done!".format(n)


tasks = [MyThread(my_fun, args=(i,)) for i in range(10)]
[task.start() for task in tasks]  # 启动多线程
[task.join() for task in tasks]   # 等待线程结束
data_list = [task.get_result() for task in tasks]
print(data_list)

如果需要获得协程的输出,以下提供两种方法

方法一:使用asyncio.wait(),使用该方法调用loop.run_until_complete函数后会返回两个结果,第一个结果就是我们想要的返回值,该值是以集合set的形式返回,且无序。

import asyncio


async def fun2(n):
    await asyncio.sleep(1)
    return "{} is done!".format(n)


loop = asyncio.get_event_loop()
tasks = [fun2(i) for i in range(1000)]
r, _ = loop.run_until_complete((asyncio.wait(tasks)))
print(r.pop().result())
loop.close()

方法二:使用asynico.gather,该方法调用loop.run_until_complete函数后直接返回我们想要的值,该值是以列表list的形式返回,且结果是按照tasks中的顺序排序。

import asyncio


async def fun2(n):
    await asyncio.sleep(1)
    return "{} is done!".format(n)


loop = asyncio.get_event_loop()
tasks = [fun2(i) for i in range(1000)]
r = loop.run_until_complete((asyncio.gather(*tasks)))
print(r)
loop.close()