【漫漫转码路】Day 46 Python中进程和线程

一、进程

进程: 资源调度的最小单位,进程是指在系统中正在运行的一个应用程序,程序一旦运行就是进程 ,进程是系统进行资源分配的独立实体,且每个进程都有独立的内存地址,进程是线程的容器
(进程是同时进行的任务,比如QQ和微信,这两个应用程序就是进程)
每个进程有独立的进程号,也就是PID
可以导入multiprocessing模块来创建进程

1、process

可以通过multiprocessing模块中的process来创建进程,进程要用start开启,
start和run: 直接调用 run() 方法时,它会在当前进程中运行,并不会创建新的进程。这种情况下,run() 方法只是一个普通的方法调用,而不是进程的启动。因此,如果想要多进程并行执行,必须使用 start() 方法来启动新的进程。
join: 进程等待,设置之后,主进程等待子进程结束之后再结束
daemon: 守护进程,开启守护进程的时候,主进程结束的时候,子进程也会被干掉,未完成的子进程不会有结果输出

    from multiprocessing import Process  # 导入模块
    import time 

    def run(name, t):
        print(f"{name} is running")
        time.sleep(t)  # 睡眠t秒
        print(f"{name} is done ,use {t}s")

    # 创建四个子进程
    p1 = Process(target= run, args=("p1", 10))  
    # 创建进程,target是目标进程函数,args是相关参数,执行的时候会把args中擦参数放到target中执行
    # args一定是元组形式
    p2 = Process(target= run, args=("p2", 8))
    p3 = Process(target= run, args=("p3", 6))
    p4 = Process(target= run, args=("p4", 4))

    ps = [p1, p2, p3, p4]

    p1.daemon = True  # 守护进程,主进程结束后,子进程也被干掉,不会打印
    for p in ps:
        p.start()  # 进程要用start运行

    # for p in ps:
    #     p.join()  # 在子程序执行完成之后再结束主进程,如果把他直接加在p.start()后面就是一个子进程完成之后再进行另一个进程

    time.sleep(2)
    print("main process is done")  # 主进程

2、Lock

Lock: 多进程情况下,如果不加锁,各进程之间管理是混乱的,相当于p1打印10张纸,p2打印8张纸,p3打印6张纸,如果不加锁,这互相之间是混乱的,可能先打印P1的2张纸,然后打印p2的3张纸,然后再打印p1的1张纸,如果对p1加了锁,那么就可以保证p1进程的连续性,就是连续打印p1的10张纸,然后打印p2
让程序更为有序
即使上锁之后,主进程也会及时结束,不会等子程序结束之后再结束
lock.acquire(): 上锁
lock.release(): 解锁
os.getpid(): 获取进程号,每个进程唯一

    from multiprocessing import Process, Lock  #导入lock
    import os, time
    def prints(lock:Lock):  # 声明lock是Lock类型
        lock.acquire()  # 上锁
        print(f"{os.getpid()} lock acquire")  # os.getpid():进程的pid号
        print(f"{os.getpid()} is running")
        time.sleep(2)  # 睡2s
        print(f"{os.getpid()} is done")
        print(f"{os.getpid()} lock release")
        lock.release()  # 解锁
    lock = Lock()
    for p in range(5):
        ps = Process(target=prints, args=(lock,))
        ps.start()  # 结果就是一个进程运行完成之后再开始第二个进程,跟单进程一样
    print("main process is done")

二、进程通信

进程通信: 各进程之间有不同的用户地址空间,一个进程的全局变量在另一个进程中看不到,进程之间的通信要通过内核,在内核中开辟一块儿缓冲区,把进程1的数据从用户空间拷贝到内核缓冲区,进程2再从缓冲区把数据读走

1、管道pipe

像管道一样,一端输送信息,一端获取信息,单向传输
也可以是双向传输
multiprocessing.Pipe(duplex=False)默认单向管道,改为True即为双向管道
管道实例化之后,就相当于[connection.Connection,connection.Connection],一个列表里两个元素,第一个是发:send,第二个是收:recv

    from multiprocessing import Process, Pipe  # 导入管道
    import time

    def producer(t, pip):
        time.sleep(t)
        pip.send("new goods")
        print(f"make goods need {t}s")

    def consumer(t, pip):
        print(f"use {pip.recv()} need {t}s")
        time.sleep(t)
        pip.send("empty")


    pip = Pipe()  # 实例化之后pipe可以认为是一个列表[connection.Connection,connection.Connection],0是发,1是收
    print(list(pip))
    p = Process(target=producer, args=(10, pip[0]))  # pip[0]是管道发的一端
    c = Process(target=consumer, args=(5, pip[1]))  # pip[1]是管道收的一端

    ps = [p, c]
    for p in ps:
        p.start()

    for p in ps:
        p.join()

    print("main process is done")

2、队列Queue

队列(Queue):创建共享的进程队列,是多进程安全的队列,可以使用Queue实现多进程之间的数据传递
Queue([maxsize])中maxsize是队列中允许的最大项数,省略则无限制
跟管道一样实现进程之间的通信
q.put(): 在队列中放一个数据
q.get(): 在队列中取一个数据

    from multiprocessing import Process, Queue  #导入队列

    def write_process(q):
        q.put(5)  # 放入一个数据5

    def read_process(q):
        print(q.get())  # 拿出来一个数据

    q = Queue()  # 不上传参数则无队列限制

    p_w = Process(target=write_process, args=(q,))
    p_r = Process(target=read_process, args=(q,))

    p_w.start()
    p_r.start()

    print("main process is done")

生产者消费者模型
单生产者,单消费者

    from multiprocessing import Process, Queue
    import time, os

    def consumer(q:Queue):  # 消费者, 消费数据,从管道/队列中取数据
        while True:
            res = q.get()
            time.sleep(2)
            print(f"p{os.getpid()} get {res}")

    def producer(q:Queue):  # 生产者,生产数据,往管道/队列中存放数据
        for i in range(10):
            time.sleep(3)
            res = "box%s" % i
            q.put(res)
            print(f"p{os.getpid()} put {res}")

    q = Queue()  # 队列实例化
    p = Process(target=producer, args=(q,))
    c = Process(target=consumer, args=(q,))

    p.start()
    c.start()

    print("main process is done!")

3、JoinableQueue

队列
q.task_done(): 相比Queue多了一个task_done(),主要作用是,在消费者处理完任务之后,会发出信号,告诉生产者可以继续生产,然后生产者收到信号之后就可以继续生产
用于多生产者多消费者模型
【具体结果的显示顺序和time.sleep()的时间有关系】

    from multiprocessing import Process, JoinableQueue, Queue
    import os, time

    # 【具体结果的显示顺序和time.sleep()的时间有关系】
    def consumer(name, q, t):
        while True:
            res = q.get()
            time.sleep(t)
            print(f"consumer{name}: {os.getpid()} --- {res}")
            q.task_done()  # 如果去掉task_done()则会在进行完一轮之后(生产3消费3之后)卡住,因为消费者【没有】发出信号表示任务已处理,生产者不会接受到继续生产的信号
            # 消费者发出信号,表示任务已处理,然后生产者可以继续生产

    def producer(name, q, t):
        for i in range(4):
            time.sleep(t)
            res = f"{os.getpid()}_{i}:{name}"
            q.put(res)
            print(f"producer : {os.getpid()} --- {res}")
            # q.join()  # 如果q.join()放在与for同级别,则会乱序
            # join的作用是等待消费者消费,如果去掉join则会出现,生产者1生产的产品1还没有被消费者消费掉,生产者1继续生产产品2
            # 正常情况是,生产者1生产的产品1被消费者消费掉之后,再继续生产产品2
        q.join()  # join()如果放在这里则表示,生产者生产一批之后,再等待消费者消费,如果放在for循环里面,表示生产者生产一个就等待消费者消费
        # join()如果放在外面表示消费者是有序的

    q = JoinableQueue()  # 如果使用JoinableQueue,不使用task_done和join则会和Queue一样,单纯是两个进程之间的通信
    # q = Queue()  # 如果使用Queue,则是单纯的两个进程之间的通信

    p1 = Process(target=producer, args=("生产者1", q, 1))
    p2 = Process(target=producer, args=("生产者2", q, 1))
    p3 = Process(target=producer, args=("生产者3", q, 1))

    c1 = Process(target=consumer, args=("消费者1", q, 1))
    c2 = Process(target=consumer, args=("消费者2", q, 1))
    c3 = Process(target=consumer, args=("消费者3", q, 1))

    c1.daemon = c2.daemon = True

    ps = [p1, p2, p3]
    cs = [c1, c2, c3]

    for p in ps:
        p.start()
        
    for c in cs:
        c.start()

    for p in ps:
        p.join()

    for c in cs:
        c.join()

    print("main process is done!")

4、信号量Semaphore

semaphore:信号量(信号标) ,用于保持0至最大值之间的一个计数值,当线程完成一次对该semaphore对象的等待时,该计数减一,当线程完成一次对semaphore对象的释放时,计数加一;
当计数为0时,则进程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态
semaphore对象的计数值大于0 ,则为signaled状态,计数等于0,则为nonsignaledz状态
semaphore = -4 ,表示有4个进程在等待着
s.acquire() : 消耗一个资源
s.release() : 释放一个资源

    from multiprocessing import Process, Semaphore  #导入信号量
    import os, time

    def prints(s):
        s.acquire()  # 消耗一个资源
        print(f"------p{os.getpid()} is acquire-----{s}")
        print(f"p{os.getpid()} is running{s}")
        time.sleep(5)
        print(f"p{os.getpid()} is done")
        print(f"------p{os.getpid()} is release-----")
        s.release()  # 释放一个资源

    s = Semaphore(2)  # Semaphore表示最多允许几个进程同时进行,等于1则相当于单进程
    for i in range(3):
        p = Process(target=prints, args=(s,))
        p.start()

5、进程池Pool

进程池:pool ([numprocess[, initializer[, initargs]]])
numprocess:要创建的进程数,如果省略,将使用默认cpu_count的值
initializer:每个工作进程启动时要执行的可调用对象,默认None
initargs:要传给initializer的参数组

如果使用pool创建numprocess为3,则会自始至终使用这三个进程,进程号不会变,不会开启其他进程
【Semaphore会产生其他进程,会有新的pid出现,但是pool不会】

p.apply (func[,arges[, kwargs]]):同步调用,在一个池进程中执行func然后返回结果

    from multiprocessing import Pool, Process  # 导入进程池
    import os, time

    def work(n):
        print(f"p{os.getpid()} is running")
        time.sleep(3)
        return n ** 2

    p = Pool(3)  # pid始终都是3个,不会有其他

    result = []

    for i in range(10):
        res = p.apply(work, args=(i, ))
        result.append(res)

    print(result)

p.apply_async(func[,arges[, kwargs]]):异步调用
同步和异步的区别:
【同步:一个进程一个进程显示; 异步:三个三个的显示】
【异步的效率更快】

异步调用得到的数据不是数字,是multiprocessing.pool.applyresult 对象,需要get()拿到
p.close(): 关闭进程池,防止进一步操作
p.join(): 等待所有工作进程退出,此方法只能在close()之后调用

    from multiprocessing import Pool
    import time, os

    def work(n):
        print(f"p{os.getpid()} is running")
        time.sleep(3)
        return n ** 2

    p = Pool(3)

    result = []

    for i in range(10):
        res = p.apply_async(work, args=(i, ))
        result.append(res)

    p.close()
    p.join()

    # print(result)  # 此时result里面的数据不是数字,而是multiprocessing.pool.applyresult 对象,需要get()拿到
    for res in result:
        print(res.get(), end=",")
    print()

三、线程及线程通信

1、GIL锁:

GIL 保证了Python解释器的内存安全,也限制了Python的多线程性能。因为同一时刻只有一个线程可以执行Python字节码,其他线程只能等待。这意味着,即使在多核CPU上,Python解释器也不能同时利用多个CPU核心来执行Python代码。
python2是元编程机制,python3是时间片轮转机制

2、线程:

进程是线程的集合,进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。
使用threading.Thread来创建线程

    import threading  # 导入threading包
    import time

    def run(n, t):
        print(f"{n} is running")
        time.sleep(t)
        print(f"{n} is done used {t}s")

    if __name__ == '__main__':
        t1 = threading.Thread(target=run, args=("t1", 5))  # 创建线程
        t2 = threading.Thread(target=run, args=("t2", 7))

        t1.start()
        t2.start()

自定义线程

    class MyThread(threading.Thread):
        def __init__(self, n, t):
            super(MyThread, self).__init__()
            self.n = n
            self.t = t

        def run(self):  # 重构run函数,必须!
            print(f"{self.n} is running")
            time.sleep(self.t)
            print(f"{self.n} is done uesd {self.t}s")

    if __name__ == '__main__':
        t1 = MyThread('t1', 5)
        t2 = MyThread('t2', 7)
        t1.start()
        t2.start()

3、守护线程

作用和进程一样,在主线程结束后,未结束的线程会被干掉,不会有输出结果
写法一: t.setDaemon(True)
写法二:t.daemon = True

    def run(n, t):
        print(f"{n} is running")
        time.sleep(t)
        print(f"{n} is done used {t}s")

    if __name__ == '__main__':
        t = threading.Thread(target=run, args=("t1", 5))
        t.setDaemon(True)  # 线程守护
        # t.daemon = True  # 线程守护的另一种写法
        t.start()
        t.join()  # 防止主线程结束杀死子线程
        print("end")

4、多线程共享全局变量

g_num = 100 # 全局变量
def work1():
    global g_num
    for i in range(3):
        g_num += 1
    print('in work1 g_num is : %d' %g_num)
    
def work2():
    global g_num
    print('in work2 g_num is : %d' %g_num)

if __name__ == '__main__':
    t1 = threading.Thread(target=work1)  # work1 使全局变量+1
    t1.start()
    time.sleep(3)
    t2 = threading.Thread(target=work2)  # work2 访问全局变量
    t2.start()
    # 结果是
    # in work1 g_num is : 103
    # in work2 g_num is : 103
    # 说明各线程之间的数据共享

5、线程锁

**lock,**同进程一样
未加锁

# 未加锁,结果不是0,多线程的时候,add和sub同时进行,会导致结果覆盖
# 相当于add拿到数据,进行运算,sub也会拿到数据进行运算,add结果返回原数据之后,sub再返回原数据就会将add的结果覆盖掉
n = 0
lock = Lock()  # 定义了lock,但是没有用
def add():
    global n, lock
    for _ in range(500000):
        n += 1
def sub():
    global n
    for _ in range(500000):
        n -= 1

if __name__ == '__main__':
    thread_add = Thread(target=add)
    thread_sub = Thread(target=sub)
    thread_add.start()
    thread_sub.start()
    thread_add.join()
    thread_sub.join()
    print('n=', n, flush=True)

加锁

# 加锁  结果是0
n = 0
lock = Lock()
def add():
    global n, lock
    for _ in range(500000):
        lock.acquire()
        n += 1
        lock.release()
def sub():
    global n
    for _ in range(500000):
        lock.acquire()
        n -= 1
        lock.release()

if __name__ == '__main__':
    thread_add = Thread(target=add)
    thread_sub = Thread(target=sub)
    thread_add.start()
    thread_sub.start()
    thread_add.join()
    thread_sub.join()
    print('n=', n, flush=True)

6、信号量

用法和进程的用法一样
但是由于线程组成了进程,所以多线程运行的时候,其实是在同一个进程中运行,
因此多线程获取到的Pid都是一样的

import threading
import time
produced = threading.Semaphore(3)  #用法和进程一样

def producer(i):
    produced.acquire()
    print(f"{i} is running")
    time.sleep(10)
    produced.release()

for i in range(20):
    t1 = threading.Thread(target=producer, args=(os.getpid(), ))
    t1.start()
    # 结果是三个三个一起蹦,但是所有的pid都是一样的

7、线程池

用法和进程池一样

from concurrent.futures import ThreadPoolExcutor  # 导入
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值