python - 进程与线程 -1

相关知识

并发与并行:

  • 并发: 只有一个CPU,一个CPU执行多个任务,把CPU运行时间分成若干时间段,每段执行不同的任务。并发解决了程序排队的问题,如果一个程序发生阻塞,其他程序仍然正常执行。
  • 并行:就是多个CPU执行任务,一个CPU执行一个,互不干涉。

进程:

进程就是一个程序(运行的实例),每个进程都有自己的地址空间。

线程:

线程是进程的一部分,只有一个线程的进程叫做单线程,在一个进程中,线程共享堆,同时每个线程有自己的栈。

  • 进程与进程:程序和程序之间的关系
  • 线程与线程:程序和程序内的小任务
  • 进程有自己的资源空间,一个进程包含若干线程,多个线程共享同一进程内的资源。
  • 线程调度与切换比进程快很多。

CPU密集型代码(循环,计算等)使用多进程

IO密集型代码(文件处理,网络爬虫等)使用多线程

阻塞和非阻塞:

  • 阻塞就是线程或进程被挂起不执行。
  • 非阻塞就是没有被挂起,继续执行。比如:一个请求要很长时间,不管这个请求,继续向下执行,当请求结束后系统会通知进程处理。

同步和异步:

同步就是阻塞,异步就是非阻塞

  • 同步:就是一个进程在执行某个请求时候,如果该请求要等很长时间才能返回,那进程就会一直等待下去。
  • 异步:就是进程继续向下执行,不管这个请求的处理。当请求结束后系统会通知进程处理。
  • 同步调用:调用方主动查询被调用方叫做同步调用
  • 异步调用:被调用方主动告诉调用方叫做异步调用

协程:

又称微线程,协程看上去是子程序,但在执行过程中在子程序内部可以中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。有点类似CPU的中断。

  • 协程有点像多线程,但执行效率高。因为是子程序切换不是线程切换(没有线程切换的开销)
  • 协程是一个线程在执行,存在于一个线程内。

Python对协程的支持是通过generator实现的。

在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。


def fun1():
    r = "null"
    while True:
        # yield 中断函数执行,返回r
        # value 接收调用者发来的数据
        value = yield r
        if not value:
            return
        print(f"传递过来的值是:{value}")
        r = "fun1_value"


def fun2(c):
    # 相当于next()
    # 恢复执行,并向生成器函数发送一个值。send()会返回生成器所产生的下一个值。如果没有下一个值就会发生StopIteration异常
    # 第一次调用要发送一个None值,
    c.send(None)
    print("ok")
    for i in range(1,5):
        r = c.send(i)
        print(f"生成器返回的结果:{r}")
    c.close()


f1 = fun1()
fun2(f1)

1. 使用multiprocessing模块创建进程

1.1 用Process类创建进程

在这个模块中有一个Process类代表一个进程对象:

 Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
  • group: 参数未使用,值始终为None
  • target: 表示当前进程启动时执行的可调用对象
  • name: 进程 实例的别名
  • args: 传递给target函数的参数(元组)
  • kwargs:传递给target函数的参数(字典)
  • daemon:
from multiprocessing import Process


def test(*args):
    print("我是子进程:")
    for i in args:
        print(i, end=', ')


def main():
    print("主进程开始")
    # 创建进程对象
    p = Process(target=test, args=(1, "abc", 2, 3, 4))
    # 启动子进程
    p.start()
    print("主进程结束")


if __name__ == '__main__':
    main()

输出:

主进程开始
主进程结束
我是子进程:
1, abc, 2, 3, 4, 

Process的实例方法:

方法 说明
is_alive() 判断进程实例是否还在执行(True, False)
start() 启动进程实例(创建子进程)
join([timeout]) 等待进程实例执行结束,或等待多少秒
terminate() 不管任务是否完成,立即终止
run() 如果没有给定target参数,执行start时,就执行对象中的run方法
属性
name 进程别名
pid 进程PID值
import os
from multiprocessing import Process
from time import sleep


def test(args):
    print(f"我是子进程:{os.getpid()}")
    sleep(args)
    print(f"子进程:{os.getpid()}结束")


def main():
    print("主进程开始")
    # 创建进程对象
    p1 = Process(target=test, args=(1,))
    p2 = Process(target=test, args=(3,))
    # 启动子进程
    p1.start()
    p2.start()
    print(f"p1.pid:{p1.pid}, p2.pid:{p2.pid}")
    p1.join()
    p2.join()
    print("主进程结束")


if __name__ == '__main__':
    main()

1.2 用Process子类创建进程

  • 定义子类,继承Process
  • 重写Process中的run方法
from multiprocessing import Process
from time import sleep


class SubProcess(Process):
    def __init__(self, time):
        Process.__init__(self)
        self.time = time
        
	# 重写run方法,调用父类的start()时会自动执行run()
    def run(self):
        print(f"子进程{self.name}开始执行")
        sleep(self.time)
        print(f"子进程{self.name}执行结束")


def main():
    print("主进程开始")
    p1 = SubProcess(2)
    p2 = SubProcess(1)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print("主进程结束")

if __name__ == "__main__":
    main()

1.3 用Pool进程池创建进程

进程池可指定大小,之后添加N个任务,进程池自动管理进程的运行。

Pool类的方法:

方法 说明
apply_async( func, args=(), kwds={}, callback=None, error_callback=None) 非阻塞方式调用func,并行执行任务
apply(func, args=(), kwds={}) 阻塞方式调用func,需要等上一个进程结束才能执行下一个进程。(不并行了)
close() 关闭Pool,不再接受新的任务
terminate() 不管任务是否完成,立即终止
join() 等待全部进程完成,必须在close, terminate之后使用
import os
import random
from multiprocessing import Pool
from time import sleep


def task(time):
    print(f"进程{os.getpid()}开始执行, time:{time}")
    sleep(time)
    print(f"\t进程{os.getpid()}执行结束")


def main():
    print("主进程开始")
    p = Pool(3)
    # 添加10个任务
    for i in range(10):
        p.apply_async(func=task, args=(random.randint(1, 5),))
    p.close()
    p.join()
    print("主进程结束")


if __name__ == "__main__":
    main()

执行结果:

主进程开始
进程3704开始执行, time:3
进程10648开始执行, time:3
进程10580开始执行, time:1
	进程10580执行结束
进程10580开始执行, time:3
	进程10648执行结束
	进程3704执行结束
进程10648开始执行, time:1
进程3704开始执行, time:4
	进程10648执行结束
	进程10580执行结束
进程10648开始执行, time:5
进程10580开始执行, time:2
	进程10580执行结束
进程10580开始执行, time:3
	进程3704执行结束
进程3704开始执行, time:4
	进程10648执行结束
	进程10580执行结束
	进程3704执行结束
主进程结束

可以看出,同时执行3个进程,完成一个后,另一个进程再开始执行。好比:有三个水龙头放水,要接10盆水一样,最多三个进程同时工作。

2. 进程间通信

每个进程都有自己的地址空间,内存,数据栈及其它记录其运行状态的辅助数据。


# 全局
global_value = 100


def add(time):
    global global_value
    print(f"进程{os.getpid()}开始运行,值为:{global_value}")
    global_value += 100
    sleep(time)
    print(f"进程{os.getpid()}运行结束, 值为:{global_value}")


def sub(time):
    global global_value
    print(f"进程{os.getpid()}开始运行,值为:{global_value}")
    global_value -= 100
    sleep(time)
    print(f"进程{os.getpid()}运行结束, 值为:{global_value}")


def main():
    print("主进程开始")
    p1 = Process(target=add, args=(1,))
    p2 = Process(target=sub, args=(2,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    global global_value
    print("global:", global_value)
    print("主进程结束")


if __name__ == "__main__":
    main()

结果:


主进程开始
进程10320开始运行,值为:100
进程10320运行结束, 值为:200
进程7300开始运行,值为:100
进程7300运行结束, 值为:0
global: 100
主进程结束

可以看出global全局变量并没有在进程之间数据共享。

2.1 通过队列实现进程间的通信

使用multiprocessing模块中的Queue实现进程之间的数据传递。

q = Queue(num)

创建一个Queue对列对象

方法 说明
q.qsize() 当前队列包含的消息数量
q.empty() 队列为空返回True
q.full() 队列满了返回True
q.get(block, timeout) 从队列中获取一条消息,然后将其从队列中移除
q.get_nowait() 队列为空(读不到数据)抛出异常
q.put(item,block,timeout) 将item写入队列,block默认为True
q.put_nowait(item) 队列满了(存不了数据)抛出异常
  • block默认为True。如果block为True,而且没有指定timeout(秒),消息队列将被阻塞,直到能读取或写入消息为止。如果设置了timeout,时间到了还没读到消息或写入消息,则抛出Queue.Empty异常
  • block为False,如果消息队列满或空,即不能写入或读取直接抛出异常。
from multiprocessing import Process, Queue
from time import sleep


def write(q: Queue):
    for i in range(10):
        message = f"{i}"
        q.put(message)
        print(f"写入消息:{message}")
    q.put("end")


def read(q: Queue):
    message = ""
    while message != "end":
        sleep(1)
        message = q.get()
        print(f"\t读取消息:{message}")


def main():
    print("主进程开始")
    q = Queue(2)
    p1 = Process(target=write, args=(q,))
    p2 = Process(target=read, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print("主进程结束")

结果:

主进程开始
写入消息:0
写入消息:1
	读取消息:0
写入消息:2
	读取消息:1
写入消息:3
	读取消息:2
写入消息:4
	读取消息:3
写入消息:5
	读取消息:4
写入消息:6
	读取消息:5
写入消息:7
	读取消息:6
写入消息:8
	读取消息:7
写入消息:9
	读取消息:8
	读取消息:9
	读取消息:end
主进程结束
posted @   叁只小羊  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示