python3 多线程 多进程 多协程

1. 多线程、多进程、多协程的对比

1.1 多进程 Process(multiprocessing)

  • 优点:可以利用多核CPU并行运算
  • 缺点:占用资源最多、可以启动的数目比线程少
  • 适用于:CPU密集型计算

1.2 多线程 Thread(threading)

一个进程包括多个线程

  • 优点:相比进程,更轻量级、占用资源少
  • 缺点:
    • 相比进程:多线程只能并发执行,只能运用单核CPU,不能利用多核CPU
    • 相比协程:启动数目有限制、占用内存资源、有线程切换开销
  • 适用于:IO密集型计算、同时运行的任务数目要求不多

1.3 多协程 Coroutine(asyncio)

一个线程包含多个协程

  • 优点:内存开销最少、启动协程数目最多
  • 缺点:支持的库有限制、代码实现复杂
  • 适用于:IO密集型计算、需要超多运行、有现成库支持的场景

2. 根据任务选择对应技术

3. 实现代码

3.1 多进程

引入包

import multiprocessing
3.1.1 创建一个进程
# 创建进程
multi = multiprocessing.Process(
    name="进程1", # 指定进程名称
    target=is_prime, # 指定进程运行的方法
    args=(i,) # 运行方法所需要的参数
)
# 运行进程
multi.start()
# 等待进程运行完毕
multi.join()
3.1.2 进程锁
import multiprocessing, time


class Account:
    def __init__(self, balance):
        self.balance = balance


# 生成锁
lock = multiprocessing.Lock()


def draw_money(account, money):
    # 使用锁
    with lock:
        if money <= account.balance:
            time.sleep(0.1)
            print("取钱成功", multiprocessing.current_process().name)
            account.balance -= money
            print("取钱成功,余额:", account.balance)
        else:
            print("取钱失败,余额:", account.balance)


def draw_money01(account, money):
    # 落锁
    lock.acquire()
    if money <= account.balance:
        time.sleep(0.1)
        print("取钱成功", multiprocessing.current_process().name)
        account.balance -= money
        print("取钱成功,余额:", account.balance)
    else:
        print("取钱失败,余额:", account.balance)
    # 开锁
    lock.release()

account = Account(1000)
a1 = multiprocessing.Process(
    target=draw_money01,
    args=(account, 800,)
)

a2 = multiprocessing.Process(
    target=draw_money01,
    args=(account, 800,)
)
a1.start()
a2.start()
3.1.3 进程池
# 引入包
from concurrent.futures import ProcessPoolExecutor
# 参数集合
PRIMES = [112239349340123] * 100
# 进程要执行的方法
def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True
# 使用进程池 map方法
# 使用with 结构 进程执行完会自动回收
with ProcessPoolExecutor() as pool:
    # result 是进程池中所有进程执行完成后的结果集合
    result = pool.map(
            is_prime, # 进程执行的方法
            PRIMES # list集合 这是一个参数集合,有多少个参数 就代表会创建多少个进程
        )
    for res in result:
        print(res)
# 使用进程池 submit方法
with ProcessPoolExecutor() as pool:
    # 进程列表
    futures = {}
    # 循环参数集合
    for i in PRIMES:
        # submit 函数第一个参数是要执行的方法,第二个为参数,submit方法是一个一个执行的
        # future 返回的不是进程执行的结果 是一个进程执行的状态 只有进程执行完才会返回进程执行的结果
        future = pool.submit(is_prime,i)
        futures[future] = i
    # as_completed 表示进程池中执行完一个就输出一个的值 不按照进程池进入的顺序输出结果
    for future in concurrent.futures.as_completed(futures):
        i = futures[future]
        print(i,future.result())
3.1.4 在Web服务中使用进程池
import json, math, flask
from concurrent.futures import ProcessPoolExecutor

# 创建一个flask对象
app = flask.Flask(__name__)


# 函数
def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True


@app.route("/is_prime/<numbers>")
def index(numbers):
    number_list = [int(x) for x in numbers.split(",")]
    # 使用map函数向线程池提交多个方法并传递参数
    result = process_pool.map(is_prime, number_list)
    # 返回结果
    return json.dumps(dict(zip(number_list, result)))


if __name__ == '__main__':
    # 创建一个进程池 进程池必须再__main__中
    process_pool = ProcessPoolExecutor()
    app.run()

3.2 多线程

引入包:

import threading
3.2.1 创建一个线程
thread = threading.Thread(
        name = "线程1",
        target=爬虫.Spider01,  # 线程执行的方法
        args=(url,)  # 执行方法需要带的参数
    )
    
# 运行线程
thread.start()
# 等待线程运行完毕
thread.join()
3.2.2 线程锁
import threading, time


class Account:
    def __init__(self, balance):
        self.balance = balance


# 生成锁
lock = threading.Lock()


def draw_money(account, money):
    # 使用锁
    with lock:
        if money <= account.balance:
            time.sleep(0.1)
            print("取钱成功", threading.current_thread().name)
            account.balance -= money
            print("取钱成功,余额:", account.balance)
        else:
            print("取钱失败,余额:", account.balance)


def draw_money01(account, money):
    # 落锁
    lock.acquire()
    if money <= account.balance:
        time.sleep(0.1)
        print("取钱成功", threading.current_thread().name)
        account.balance -= money
        print("取钱成功,余额:", account.balance)
    else:
        print("取钱失败,余额:", account.balance)
    # 开锁
    lock.release()
account = Account(1000)
a1 = threading.Thread(
    target=draw_money01,
    args=(account, 800,)
)

a2 = threading.Thread(
    target=draw_money01,
    args=(account, 800,)
)
a1.start()
a2.start()
3.2.3 线程池
# 引用包
from concurrent.futures import ThreadPoolExecutor

# 线程池需要的方法
urls = [
    f"https://www.cnblogs.com/#{page}"
    for page in range(1,20+1)
]

# 线程要执行的方法
def Spider(url):
    req = requests.get(url)
    return req.text

# 使用线程池 map方法
with ThreadPoolExecutor() as pool:
    # map函数第一个参数为要执行的方法,第二个参数为参数列表,参数列表的数量决定方法执行的次数
    htmls = pool.map(Spider,urls)
    # zip方法是将第一个list集合的每个值与集合中的每个值拼接成元组(两个集合数量要一致)
    htmls = list(zip(urls,htmls))
    for url,html in htmls:
        print(url,len(html))
# 使用线程池 submit方法
with ThreadPoolExecutor() as pool:
    futures = {}
    for url,html in htmls:
        # submit 函数第一个参数是要执行的方法,第二个为参数,submit方法是一个一个执行的
        future = pool.submit(html_parse,html)
        futures[future] = url
    # 这种获取返回值的方法是等待线程执行完再输出返回值
    # for future in futures:
    #     url = futures[future]
    #     print(url,future.result())
    # as_completed 方法是线程池中执行完一个方法输出一个返回值,可能会造成顺序不一致
    for future in concurrent.futures.as_completed(futures):
        url = futures[future]
        print(url,future.result())
3.2.4 在Web服务中使用线程池
import json,time,flask
from concurrent.futures import ThreadPoolExecutor

# 创建一个flask
app = flask.Flask(__name__)
# 创建一个线程池
pool = ThreadPoolExecutor()

# 延时方法
def api_def():
    time.sleep(0.1)
    return "api_def"


# 延时方法
def file_def():
    time.sleep(0.2)
    return "file_def"


# 延时方法
def db_def():
    time.sleep(0.3)
    return "db_def"


# 首页
@app.route("/")
def index():
    # 往线程池添加一个执行的函数
    file_result = pool.submit(file_def)
    # 往线程池添加一个执行的函数
    api_result = pool.submit(api_def)
    # 往线程池添加一个执行的函数
    db_result = pool.submit(db_def)
    # 获取执行的结果
    return json.dumps({
        "file_result": file_result.result(),
        "api_result": api_result.result(),
        "db_result": db_result.result()
    })


if __name__ == '__main__':
    app.run()

3.3 多协程

引入包:

import asyncio
3.3.1 使用多协程
# 创建协程方法
async def main():
    print("这是")
    await asyncio.sleep(1)
    print("多协程")
# 运行
asyncio.run(main())
3.3.2 使用协程池爬取网页

需要用到包:

pip install aiohttp

代码:

import asyncio, aiohttp, time

# 创建一个协程函数
async def async_craw(url):
    # 创建客户端对象
    async with aiohttp.ClientSession() as session:
        # 根据url获取对象
        async with session.get(url) as resp:
            # 获取结果
            result = await resp.text()
            # 返回结果
            print(f"url:{url},text:{len(result)}")


# 获取协程池
pool = asyncio.get_event_loop()

# url列表
urls = [
    f"https://www.cnblogs.com/#{page}"
    for page in range(1,20+1)
]
# 创建任务列表
task = [
    pool.create_task(async_craw(url)) for url in urls
]

start = time.time()
# 等待任务列表全部执行完成
pool.run_until_complete(asyncio.wait(task))
end = time.time()
print("time:", end - start)
3.3.3 控制协程池数量
import asyncio, aiohttp, time, 爬虫

# 设置并发数量
semaphore = asyncio.Semaphore(5)
async def async_craw(url):
    # 使用并发数量
    async with semaphore:
        # 创建客户端对象
        async with aiohttp.ClientSession() as session:
            # 根据url获取服务器返回对象
            async with session.get(url) as resp:
                # 获取服务器返回内容
                result = await resp.text()
                print(f"url:{url},text:{len(result)}")

# 协程池
pool = asyncio.get_event_loop()

# 创建任务集合
task = [
    pool.create_task(async_craw(url)) for url in 爬虫.urls
]


start = time.time()
# 执行任务池 并等待任务池执行完成
pool.run_until_complete(asyncio.wait(task))
end = time.time()
print("time:", end - start)

posted @ 2023-10-29 15:37  Simian_2018_12_22  阅读(114)  评论(0编辑  收藏  举报