实现多线程/多进程/多并发

 1. 什么是多线程/多进程/多并发?

1.1 多线程

        多线程是在单个进程内创建多个线程来执行任务的技术。每个线程都拥有自己的独立的执行流,可以并发地执行任务。多线程适用于 CPU 密集型任务,例如计算、数据处理等。

优点:

  • 开销小,创建和销毁线程的速度快。
  • 线程之间共享内存,可以方便地进行数据交换。

缺点:

  • 受限于单个进程的资源,线程之间无法充分利用多核 CPU 的优势。
  • 线程之间可能会出现竞争和死锁问题。
import threading
import time

def task(name):
    print(f"线程 {name} 开始执行")
    time.sleep(2)
    print(f"线程 {name} 执行结束")

threads = []
for i in range(5):
    #创建线程
    thread = threading.Thread(target=task, args=(i,))#传入 task 函数和线程名称作为参数,
args时task的入参
    #添加线程
    threads.append(thread)
    #启动线程
    thread.start()

#等待线程结束,代码使用 for 循环遍历 threads 列表,并使用 thread.join() 
方法等待每个线程结束。这将阻塞主线程,直到所有线程都执行完成
for thread in threads:
    thread.join()

threading.Thread 的入参

  • threading.Thread 类用于创建和管理线程。它接受以下参数:
  • target: 线程要执行的函数。
  • args: 传递给 target 函数的参数元组。
  • kwargs: 传递给 target 函数的关键字参数字典。
  • name: 线程的名称。
  • daemon: 是否将线程设置为守护线程。默认值为 False。
  • verbose: 是否打印线程调试信息。默认值为 None。

位置参数和关键字参数

(1) 位置参数 (positional):  传参时不带"变量名=", 顺序不可变,  需要按照函数定义时参数的顺序进行传参.

(2) 关键字参数(keyword): 使用key=value形式传参,  传参时前面加上"变量名=", 顺序可变, 可以不按照函数定时参数的顺序进行传参.

(3) 可变位置参数(*args):  接收到的所有按照位置参数方式传递进来的参数, 是一个元组类型.

def getsum(*num) :
    sum = 0
    for n in num :
        sum = sum + n * n
    return sum

如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

(4) 可变关键字参数(**kw):  接收到的所有按照关键字参数方式传递进来的参数, 是一个字典类型.

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

1.2 多进程

        多进程是在多个进程内创建多个进程来执行任务的技术。每个进程都有自己的独立的内存空间和执行流,可以并发地执行任务。多进程适用于 IO 密集型任务,例如网络请求、文件读写等。优点:

  • 可以充分利用多核 CPU 的优势,提高程序的执行效率。
  • 进程之间相互隔离,不会出现竞争和死锁问题。

缺点:

  • 开销较大,创建和销毁进程的速度慢。
  • 进程之间无法直接共享内存,需要通过其他方式进行数据交换。        
import multiprocessing
import time

def task(name):
    print(f"进程 {name} 开始执行")
    time.sleep(2)
    print(f"进程 {name} 执行结束")

processes = []
for i in range(5):
    process = multiprocessing.Process(target=task, args=(i,))
    processes.append(process)
    process.start()

for process in processes:
    process.join()

1.3 多并发

        多并发是指在一个程序中同时执行多个任务的技术。多并发可以利用多线程、多进程或其他技术来实现。多并发适用于需要同时处理多个请求或任务的场景,例如 Web 服务器、数据库系统等。

优点:

  • 可以提高程序的响应速度,并行处理多个任务。
  • 可以充分利用系统的资源,提高资源利用率。

缺点:

  • 需要考虑并发控制和同步问题,避免出现数据竞争和死锁问题。
  • 需要合理分配系统资源,避免出现资源瓶颈问题。

1.并发执行和并行执行
    并行(parallel): 指的是互不干扰的在同一时刻做多件事,对应Python中的就是多进程(multi-processing),可以利用多核处理器的优势,通常应用于 CPU heavy 的场景,比如计算密集型任务。
    并发(concurrency):并发是指在单个处理器上交替执行多个任务。这并不意味着任务是同时执行的,而是意味着任务交替执行,并且给人的感觉是它们是同时执行的,对应Python中就是多线程(multi-threading)或者协程(Coroutine),通常应用于 I/O 操作频繁的场景,比如发起网络请求。

    并行和并发之间的主要区别在于,并行需要多个处理器或核心,而并发只需要一个处理器或核心。并行可以提高程序的执行速度,而并发可以提高程序的响应速度。


2. 同步调用和异步调用
同步调用和异步调用是提交任务的两种方式。

同步调用:提交任务,原地等待任务执行结束,拿到任务返回结果。再执行下一行代码,会导致任务串行执行。
异步调用:提交任务,不进行原地等待,直接执行下一行代码,任务并发执行。


3. 阻塞状态和非阻塞状态
阻塞运行和非阻塞运行,是程序的运行状态。

阻塞:程序遇到IO操作时,进行原地等待,即程序处于阻塞态。
非阻塞:程序没有进行IO操作时,程序处于运行态,即就绪态。


4.进程池和线程池
进程池和线程池,是用于控制进程数或线程数的。

如果服务器开启的进程数或线程数,随并发的客户端数目单调递增,服务器就会承受巨大的压力,于是使用“池”的概念,对服务端开启的进程数或线程数加以控制。

进程池:用来存放进程的"池"
线程池:用来存放线程的"池"
当服务器收到客户端的请求时,从池子中拿出线程或者进程来处理,处理完,再把线程或者进程放入池子中。

可以引入线程池和进程池

线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。

from concurrent.futures import ThreadPoolExecutor, as_completed

import requests


# func函数
def download_one(url):
    resp = requests.get(url)
    print('Read {} from {}'.format(len(resp.content), url))
    return {'url': url, 'content_length': len(resp.content)}

# 多线程【实现方法一】
def multi_tread(sites_list):
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        executor.map(download_one, sites_list)

# 多线程【实现方法二】
def multi_tread(sites):
    with ThreadPoolExecutor(max_workers=5) as executor:
        to_do = []
        for site in sites:
            future = executor.submit(download_one, site)
            to_do.append(future)

        for future in as_completed(to_do):
            future.result()

if __name__ == '__main__':
    sites = [
        'https://golang.google.cn/',
        'https://www.python.org/',
        'http://www.php.net/',
    ]

    multi_tread(sites)

方法一/方法二详解

一、
    executer.map()与Python内置的map()函数类似,表示对sites_list中的每⼀个元素,并发地调⽤函数download_one()

二、

    这⾥需要两个循环,第一个循环对每个网站调⽤ executor.submit()产生一个Future对象future并放入to_do中等待执⾏

    第二个循环,是对于执行完成的future通过result()方法获取结果。as_completed(fs)是针对给定的future迭代器fs,在其完成后,返回完成后的迭代器。

    不过,这⾥要注意,future 列表中每个 future 完成的顺序,和它在列表中的顺序并不⼀定完全⼀致。到底哪个先完成、哪个后完成,取决于系统的调度和每个future的执⾏时间。

通常建议使用executor.map()方法,既简单又高效,而且返回执行结果的顺序,依然与传入参数的顺序保持一致

  • 17
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值