Python进程,线程及协程

一:进程

进程:进程是对运行时程序的封装,是系统资源调度和分配的基本单位

1.认识进程

使用进程一般使用multiprocessing模块的Process类来创建。

import os
import time
from multiprocessing import Process

def run():
    while True:
    	# os.getpid()获取当前进程的pid,os.getppid()获取父进程的pid
        print('study is bad--',os.getpid(),'--',os.getppid())
        time.sleep(1)

if __name__ == '__main__':
    print('启动主进程')
    # args 传参
    p = Process(target=run, args=()) # 将需要进程执行的函数对象传进去
    # 启动进程
    p.start()
    while True:
        print('study is terrible',os.getpid())
        time.sleep(1.2)

在这里插入图片描述
这里因为主线程的睡眠时间是1.2秒,子进程是1秒,所有有时候执行两次子进程才执行一次父进程

2.设置父子进程执行顺序

1中的执行的时候会出现一个问题,因为子进程没有加入到主进程,所有会出现主进程执行完了,子进程还在执行的情况,这个给一个处理的方法。

import os
import time
from multiprocessing import Process

def run():
    # while True:
    print('study is bad--',os.getpid(),'--',os.getppid())
    time.sleep(3)
    print('子进程结束')

if __name__ == '__main__':
    print('启动主进程')
    # args 传参
    p = Process(target=run, args=())
    # 启动进程
    p.start()

    # 父进程等待子进程结束
    p.join()
    print('父进程结束。')

在这里插入图片描述
这里join()方法,是使主进程在join的地方阻塞,只有当阻塞的子进程执行完,才会继续执行父进程

3.全局变量在多进程中不能共享

import os
import time
from multiprocessing import Process
num = 100
def run():
    # while True:
    print('study is bad--',os.getpid(),'--',os.getppid())
    # time.sleep(3)
    # 相当于新定义了一个 num = 100
    global  num
    num += 1
    print('func num %d'%id(num))
    print('子进程结束',num)

if __name__ == '__main__':
    print('启动主进程')
    # args 传参
    p = Process(target=run, args=())
    # 启动进程
    p.start()
    # 每个进程都有自己的数据段,代码段,堆栈区,所以父子进程全局变量不能共用
    # 父进程中 全局变量共用
    # 父进程等待子进程结束
    p.join()
    print('num %d'%id(num))
    print('父进程结束。',num)

在这里插入图片描述

这里想改变全局变量num的值,没有改变成功,是因为每一个进程在新建的时候,都会开辟一块内存,在这个空间会有自己的数据段,代码段,堆栈区,以及变量num会拷贝一个新的。所以两个num的存储位置不一致。

4.使用进程池

import os
import time
from multiprocessing import Process,Pool
import random


def run(num):
    # while True:
    start = time.time()
    print(f'子线程{num},进程号{os.getpid()}启动')
    time.sleep(random.choice([1,2,3]))
    end = time.time()
    print(f'子线程{num},进程号{os.getpid()}结束,耗时{end - start}')


if __name__ == '__main__':
    print('启动主进程')
    # Pool 默认为cpu核心数
    pp = Pool(2)
    for i in range(4):
        # 创建进程 放入线程池
        pp.apply_async(run,args=(i,))
    pp.close()
    pp.join()
    print('父进程结束。')

在这里插入图片描述
使用进程池,新建进程池对象的时候,传入cpu数量(几核),一核一次只能执行一个任务,这创建了一个双核的进程池,但是有4个线程,所以只有执行完2个进程,才会继续执行另外2个进程

5.进程间通信

import os
import time
from multiprocessing import Process,Queue


def write(q):
    print('写消息',os.getpid())
    # time.sleep(5)
    for item in ['A','B','C','D','E','F','G']:
        time.sleep(0.5)
        q.put(item)
    print('写进程完成~~')

def read(q):
    print('读进程',os.getpid())
    while True:
        time.sleep(0.5)
        print(q.get(True))
    print('读进程结束')

if __name__ == '__main__':
    print('启动主进程')
    q = Queue()
    # args 传参
    p1 = Process(target=write, args=(q,))
    p2 = Process(target=read, args=(q,))
    # 启动进程
    p1.start()
    p2.start()
    print("主进程还在继续")
    # 父进程等待子进程结束,阻塞
    p1.join()
    print('p1 结束')
    # p2是死循环,手动结束
    p2.terminate()
    print('父进程结束。')

在这里插入图片描述
使用了队列(Queue)进行通信。

二:线程

线程:是进程的子任务,cpu调度和分配的基本单位,实现进程并发

1.认识线程

import threading
import time


def run():
    print('打印')
    time.sleep(2)
    print('子线程%s结束'%(threading.current_thread().name))


if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
    print('启动主线程(%s)'%(threading.current_thread().name))

    # 创建子线程
    t = threading.Thread(target=run,name='rootThread')
    t.start()

    # 等待线程结束
    t.join()

    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述

2.线程间共享数据

import threading
import time

num = 0

def run(n):
    global num
    for i in range(1000000):
        num += n
        num -= n

if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
    print('启动主线程(%s)'%(threading.current_thread().name))

    # 创建子线程
    t1 = threading.Thread(target=run,args=(6,))
    t2 = threading.Thread(target=run, args=(9,))
    t1.start()
    t2.start()

    # 等待线程结束
    t1.join()
    t2.join()
    print(num)

    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述
期望的结果是0。但是两个线程操作同一个变量,没有限制顺序,同时进行,就有可能出问题

ps:多线程和多进程的区别,多进程中,每一个进程都享有自己独立的数据段,代码段,对战区间,进程间互不影响
多线程中,所有变量都可以被共享(一个进程内),线程存在数据共享导致数据混乱

3.使用线程锁解决数据混乱

import threading
import time

num = 0

# 锁对象
lock = threading.Lock()

def run(n):
    global num
    for i in range(1000000):
        # 锁
        # 阻止了多线程的并发执行,包含锁的部分代码只能以单线程模式执行,效率下降很多
        # 线程可以有多个锁,不同线程持有不同的所,并试图获取其他的锁,有可能导致死锁。导致多个线程挂起,只能通过操作系统终止
#         lock.acquire()
#         try:
#             num += n
#             num -= n
#         finally:
# #           释放锁
#             lock.release()
#       同效果
        with lock:
            num += n
            num -= n


if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
    print('启动主线程(%s)'%(threading.current_thread().name))

    # 创建子线程
    t1 = threading.Thread(target=run,args=(6,))
    t2 = threading.Thread(target=run, args=(9,))
    t1.start()
    t2.start()

    # 等待线程结束
    t1.join()
    t2.join()
    print(num)

    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述
使用了锁的机制,当访问共享的资源的时候,获得锁的线程先执行,等待执行完了,释放锁,给第二个线程,第二个线程才开始执行,同理多个线程等待的情景

4.线程局部变量(threadloacl)

import threading
import time

num = 0

# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()

def run(m,n):
    m.x += n
    m.x -= n

def func(n):
    # 创建线程的局部变量x,每个线程都拥有互不影响名称相同的x,num这个变量只起到了赋值作用
    local.x = num
    for i in range(1000000):
        run(local,n)
    print('子线程%s,结果为%d'%(threading.current_thread().name,local.x))

if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
    print('启动主线程(%s)'%(threading.current_thread().name))

    # 创建子线程
    t1 = threading.Thread(target=func,args=(6,))
    t2 = threading.Thread(target=func, args=(9,))
    t1.start()
    t2.start()

    # 等待线程结束
    t1.join()
    t2.join()
    print(num)

    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述

5.使用信号量控制线程数量

import threading
import time

num = 0

# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()

# 一次只让2个线程执行
sem = threading.Semaphore(2)

def run():

    with sem:
        for i in range(5):
            time.sleep(1)
            print('%s--%d' % (threading.current_thread().name, i))


if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程

    for i in range(5):
        threading.Thread(target=run,).start()

    # 等待线程结束
    print(num)

    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述
可以看出,设置了信号量(2)以后,只能执行指定数量的线程(2),只有当线程执行完以后,才会继续等待的线程

6.凑够一定数量才能执行

import threading
import time

num = 0

# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()

# 凑够4个才能继续
bar = threading.Barrier(4)

def run():

    print('%s--start'%(threading.current_thread().name))
    time.sleep(1)
    # 凑够4个才会向下执行,不然就等待
    bar.wait()
    print('%s--end'%(threading.current_thread().name))


if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程

    for i in range(5):
        threading.Thread(target=run,).start()

    # 等待线程结束
    print(num)

    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述
使用threading.Barrier设置线程数,只有线程的数量达到要求,才能使程序继续运行,并且等待下一组线程,不然就等待

7.延时执行

import threading
import time
from datetime import datetime

num = 0

# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()

# 凑够4个才能继续
bar = threading.Barrier(4)

def run():

    print('%s--start'%(threading.current_thread().name))



if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
    print(datetime.now())
    t = threading.Timer(3,run)
    t.start()
    t.join()
    print(datetime.now())
    print('主线程%s结束'%(threading.current_thread().name))

在这里插入图片描述

使用threading.Timer设置等待时间,线程会先等待设定时间,之后才会运行子线程的部分

8.事件触发(threading.Event)

import threading
import time

num = 0



def func():

    event = threading.Event()
    def run():
        for i in range(5):
#           阻塞 等待事件触发
            event.wait()
#             重置
            event.clear()
            print('子线程%d'%i)
    t = threading.Thread(target=run).start()
    return event


if __name__ == '__main__':
    # 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
    e = func()

    for i in range(5):
        time.sleep(1)
        # 触发事件
        e.set()

在这里插入图片描述

9.生产者,消费者

import random
import threading,queue,time

def product(i,q):
    while True:
        num = random.randint(0,1000000)
        q.put(num)
        print('生产者%d生产了%d数据放入了队列'%(i,num))
        time.sleep(3)
#     任务完成
    q.task_done()

def customer(i,q):
    while True:
        item = q.get()
        if item is None:
            break
        print('消费者%d消费了%d数据'%(i,item))
        time.sleep(3)
#     任务完成
    q.task_done()


if __name__ == '__main__':
    q = queue.Queue()

    # 启动生产者
    for i in range(4):
        threading.Thread(target=product,args=(i,q)).start()

    # 启动消费者
    for i in range(4):
        threading.Thread(target=customer,args=(i,q)).start()

在这里插入图片描述

10.线程调度

import threading,time

# 线程条件变量
cond = threading.Condition()
def run1():
    with cond:
        for i in range(0,10,2):
            print(threading.current_thread().name,i)
            time.sleep(1)
            print('run1 start wait')
            cond.wait()
            print('run1 end wait')
            print('run1 start notify')
            cond.notify()
            print('run1 end notify')

def run2():
    with cond:
        for i in range(1,11,2):
            print(threading.current_thread().name,i)
            time.sleep(1)
            print('run2 start notify')
            cond.notify()
            time.sleep(1) # run1不会执行,run1被唤醒,还在等待锁
            print('run2 end notify')
            print('run2 start wait')
            cond.wait()
            print('run2 end wait')

if __name__ == '__main__':
    threading.Thread(target=run1).start()
    threading.Thread(target=run2).start()

在这里插入图片描述
使用了threading.Condition,只有满足条件之后才会执行,在使用wait()的时候,会释放锁,并进入挂起的状态,只有使用notify()方法取消挂去状态,进入等待锁执行的状态。等拿到锁了才能继续运行。
ps:
多任务
多进程,一个主进程,多个子进程,子进程故障,不影响其他进程,但是开销很大
多线程,效率相对于进程,要快很多,但是线程故障一个,可能导致整个进程崩溃

三:协程

协程:是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

1.了解协程

def A():
    print("Astart")
    B()
    print('Aend')


def B():
    print("Bstart")
    C()
    print('Bend')


def C():
    print("Cstart")

    print('Cend')

A()

在这里插入图片描述

  • 子程序/函数:在所有语言中都是层级关系,比如A调用了B,然后B又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕

  • 概述:看上去是子程序,但执行过程中,在子程序的内部可中断,然后转而执行别的子程序,不是函数调用。
    执行结果类似于多线程的结果,但是只有一个线程,执行效率极高

2.协程原理

def run():
    print(1)
    yield 10
    print(2)
    yield 20
    print(3)
    yield 30

m = run()

print('*****')
print(next(m))

print(next(m))
print(next(m))
# print(next(m))
print('*****')

在这里插入图片描述

  • 实现协程的关键在于使用yield,只有通过next才能向下执行,并在下个yield等待
  • 协程:控制函数的阶段执行,节约线程或进程的切换

3.数据传输

def run():
    data = ''

    '''
    第二次传值赋第一次的yield的那一行赋值,并在第二个yield等待
    '''
    print('start')
    r = yield 'aa'
    print('1 end')
    print(1,r,data)
    r = yield 'ee'
    print(2,r,data)
    r = yield data
    print(3,r,data)
    r = yield data
    print(4,r,data)

m = run()
print('*****')
print(m.send(None))
print(m.send('a'))
print(m.send('b'))
print(m.send('c'))

print('*****')

在这里插入图片描述
执行方式:

  1. 带有yield的函数运行会返回一个生成器(generator),这里在调用send()方法才会直行到下一个yield的地方。
  2. send()会传入一个参数,从上到下,从右向左,直到达到yield就停止,并将yield右边的数据作为send()执行完的结果返回。
  3. 第一次send None的时候,达到yield就没有继续了,r实际上还没有赋值成功,只有第二次send a的时候r才赋值成功,这里是稍微需要花点时间理解一下的地方

4.协程实现生产者消费者

import random
def product(c):
    c.send(None)
    for i in range(5):
        print('send start')
        n = random.randint(0,100)
        c.send(n)
        print('send end %d'%n)
    c.close()

def customer():
    data = ''
    while True:
        n = yield data
        if not n:
            return
        print('消费者消费了%d'%n)
        data = '200'

c = customer()
product(c)

在这里插入图片描述
完结撒花~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值