Day20笔记,进程线程协程

一、进程

1.多个进程中的全局变量
#进程的特点:独立性,并发性,动态性

#多个进程间的全局变量:独立性【进程之间是相互独立的,资源不共享】

from multiprocessing import  Process
from time import  sleep

#全局变量
num = 100

def run():
    print("子进程开始")

    global num
    num += 1

    print("子进程中的num=%d" % (num))

    print("子进程结束")

if __name__ == "__main__":
    print("父进程开始")

    p = Process(target=run)
    p.start()

    #等待子进程结束之后父进程才结束,进程在执行的过程中是由系统分配的,cpu调度
    p.join()

    sleep(3)

    """
    说明:在子进程中修改全局变量对父进程中的全局变量没有影响
    工作原理:在创建子进程对象的时候,实际上已经对全局变量做了一个备份,
            父进程中的num与子进程中是完全不同的两个变量
    结论:进程是系统中程序执行和资源分配的基本单位,每个进程都有自己的数据段,代码段和堆栈段【进程之间资源不共享】
    """
    print("父进程中的num=%d" % (num))
    print("父进程结束")
2.启动大量子进程
"""
启动多个进程:
    a.创建多个Process类
    b.如果要启动大量的子进程,可以用进程池的方式批量创建子进程
"""
from  multiprocessing import  Pool
import time,os,random

def run(name):
    print("子进程-%s-启动:%s" % (name,os.getpid()))

    start = time.time()

    time.sleep(random.randint(1,3))

    end = time.time()

    print("子进程-%s-结束:%s,耗时:%.2f" % (name, os.getpid(),end - start))

if __name__ == "__main__":
    print("父进程启动")

    #1.创建进程池的对象
    """
    语法:Pool(num),num表示可以同时执行的进程数量,num默认为当前cpu核心数量
    """
    p = Pool(4)

    #2.通过循环的方式创建多个子进程,添加到进程池中进行统一的管理
    for i in range(10):
        #多个子进程处理同一件任务
        p.apply_async(run,args=(i,))

    #3.
    """
    a.进程池对象调用join,会等待进程池中的所有的子进程结束完毕,父进程才结束
       但是,在进程池中,必须调用join
    b.在调用join之前必须先调用close,调用close之后就不能再添加新的进程对象
    """
    p.close()
    p.join()

    print("父进程结束")


3.进程之间的通信
"""
Process之间是需要通信的,进程之间本来是相互独立的,但是,可以借助于Queue【队列】完成数据交换
"""

from multiprocessing import  Process,Queue
import  os,time

list1 = ["A","B","C"]

#1.写数据的任务
#q是队列
def write(q):
    print("进程%s开始,开始写数据" % (os.getpid()))

    for value in list1:
        print("add %s to queue....." % (value))

        #向队列中添加数据
        q.put(value)

        time.sleep(1)

    print("进程%s结束" % (os.getpid()))

#2.读数据的任务
def read(q):
    print("进程%s开始,开始读数据~~~~~" % (os.getpid()))

    for i in range(len(list1)):
        #从队列中获取数据
        value = q.get(True)
        print("get %s from queue....." % (value))

    print("进程%s结束~~~~~" % (os.getpid()))

if __name__ == "__main__":
    print("父进程开始")

    #创建队列对象
    q = Queue()

    #创建子进程的对象
    pw = Process(target=write,args=(q,))
    pr = Process(target=read, args=(q,))

    pr.start()
    pw.start()

    #time.sleep(7)

    pr.join()
    pw.join()

    print("父进程结束")



二、线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成

在一个进程的内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”叫做线程

线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务,每一个线程都共享一个进程中的资源

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,也不能决定执行多长时间

而且线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程

1.创建并启动线程
"""
process:进程    thread:线程

_thread模块:提供了低级别的,原始的线程【只是功能比较有限】
threading模块:高级模块,对_thread模块进行封装,并提供了_thread模块没有的功能

通过threading模块中Thread类创建线程对象,和进程类似,线程对象同样需要调用start
"""
import  threading,time


#3.子线程的任务
def run():
    print("子线程启动:%s" % (threading.current_thread().name))

    time.sleep(2)

    print("子线程结束:%s" % (threading.current_thread().name))

def run1(num):
    print("子线程启动:%s" % (threading.current_thread().name))

    print(num)
    time.sleep(2)

    print("子线程结束:%s" % (threading.current_thread().name))


if __name__ == "__main__":
    #1.程序一旦启动,就会启动一个进程【父进程/主进程】,一个进程至少启动一个线程,
     # 默认启动的线程被称为主线程
    #threading.current_thread()获取当前正在执行的线程对象,该对象的name属性表示线程的名称
    #主线程的名称默认为MainThread
    print("主线程启动:%s" % (threading.current_thread().name))

    #2.在主线程中创建子线程
    """
    Thread(target,name,args)
    """
    #2.1不指明名称,使用默认名称,子线程默认的名称为:Thread-1,Thread-2....
    # t1 = threading.Thread(target=run)
    # t1.start()

    #2.2自定义线程名称
    # t2 = threading.Thread(target=run,name="子线程-果冻")
    # t2.start()

    #2.3任务函数有参数
    t3 = threading.Thread(target=run1, name="子线程-土豆",args=(45,))
    t3.start()

    # time.sleep(4)

    # 4.和进程类似,子线程也可以完成合并
    t3.join()

    print("主线程结束:%s" % (threading.current_thread().name))
2.线程中的共享数据
#进程:资源不共享,  线程:资源共享
"""
在多线程中,全局变量由所有线程共享,所以,任何一个全局变量可以被任意一个线程修改,
线程之间共享数据最大的危险在于:容易将数据改乱
"""

import  threading,time

#假设这是你的银行存款
balance = 0

def change(n):
    #先存后取,理论上上应该为0
    global balance
    balance = balance + n
    balance = balance - n

def run(m):
    for i in range(1000000):
        change(m)

if __name__ == "__main__":
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("balance=%d" % (balance))

"""
问题:
    理论上结果为0,但是,线程的调度由操作系统决定的
    当t1,t2交替执行时,只要循环次数够多,balance的值就不一定为0,有可能是正数,也有可能是负数
    
问题的本质:使用多线程解决问题出现的临界资源问题

原因分析:
    因为高级语言的一条语句在cpu执行的时候是若干条语句
    举例:balance = balance + n分为两步执行:
        a.计算balance + n,存入到临时变量中 ,x = balance + n
        b.将临时变量的值给balance赋值,balance = x
        
    而两个线程各自有自己的x
    
    1.正常
    t1:x1 = balance + 5
    t1:balance = x1
    t1:x1 = balance - 5
    t1:balance = x1    
    
    t2:x2 = balance + 8
    t2:balance = x2
    t2:x2 = balance - 8
    t2:balance = x2  
     
    balance = 0
    
    #2.
    t1:x1 = balance + 5   #x1 = 5
    
    t2:x2 = balance + 8   #x2 = 8
    t2:balance = x2       #balance = 8
    
    t1:balance = x1       #balance = 5
    t1:x1 = balance - 5   #x1 = 0
    t1:balance = x1       #balance = 0
    
    t2:x2 = balance - 8    #x2 = -8
    t2 = balance = x2      #balance = -8
    
    结果:balance = -8
    
  因为修改balance需要多条语句,当执行语句时,线程可能会中断 ,
  从而导致多个线程把同一个对象的内容改乱了      
        
 
解决方案: 可以给临界资源上一把锁
        给临界资源上锁,当一个线程访问的时候,
        其他的线程就需要在锁外面等待,知道锁被释放之后,获得该锁的线程才能再次访问      
"""
3.线程锁
import  threading,time

#假设这是你的银行存款
balance = 0

#创建一个锁对象
lock = threading.Lock()

def change(n):
    #先存后取,理论上上应该为0
    global balance
    balance = balance + n
    balance = balance - n

def run(m):
    for i in range(1000000):
        #获取锁/持有锁
        lock.acquire()

        try:
            # 更改临界资源
            change(m)
        #不管changge()有没有异常,finally中的代码都会被执行,锁需要被释放
        finally:
            # 释放锁
            lock.release()

if __name__ == "__main__":
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("balance=%d" % (balance))
import  threading,time

#假设这是你的银行存款
balance = 0

#创建一个锁对象
lock = threading.Lock()

def change(n):
    #先存后取,理论上上应该为0
    global balance
    balance = balance + n
    balance = balance - n

def run(m):
    for i in range(1000000):
        #with lock的好处:可以自动上锁和解锁
        with lock:
            change(m)

if __name__ == "__main__":
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("balance=%d" % (balance))
4.ThreadLocal
"""
一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见
不会影响其他线程,而全局变量则必须加锁
"""
import  threading

balance = 0

#创建ThreadLocal对象,每个线程有独立的存储空间,
# 每个线程对Threadlocal对象可以读写,但是互不影响
local = threading.local()

def change(x,n):
    x = x + n
    x = x - n

def run(m):
    """
    local是一个全局变量,每个线程对象对local都可以读写y属性,但是互不影响
    注意:y可以是一个任意的标识符,如local.a等

    帮助理解:local是一个全局变量,local.y是每个线程的局部变量
    可以任意读写而互不干扰,也不用管理锁
    """
    local.y = balance

    for i in range(1000000):
        change(local.y,m)

if __name__ == "__main__":
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("balance=%d" % (balance))


"""
多个线程访问同一个全局变量:
    a.线程锁
    b.threadlocal:解决了参数在一个线程中各个函数之间相互传递的问题
"""
5.定时线程
import  threading

def run():
    print("hellohello")

if __name__ == "__main__":
    print("父线程启动")

    #创建延时线程并启动
    t = threading.Timer(5,run)
    t.start()

    print("父线程结束")
6.线程间的通信
#需求:一个线程向队列中添加数据,一个线程从队列中获取数据

"""
生产者消费者设计模式
    是通过一个容器解决生产者和消费者之间的耦合问题,
    生产者和消费者彼此之间不直接通讯,而是通过队列来进行通讯
    所以生产者生产完数据之后不用等待消费者处理,,直接扔给队列,
    消费者不找生产者获取数据,而是直接从队列中获取,队列相当于是一个缓冲区,平衡了生产者和消费者的能力

"""

#进程间的通信:multiprocessing模块中的Queue

import  threading,queue,random,time

def write(id,q):
    while True:
        num = random.randint(0,10000)
        q.put(num)
        print("生产者%d生产了数据%d放入了队列" % (id,num))
        time.sleep(1)
    #任务完成
    q.task_done()

def read(id,q):
    while True:
        v = q.get()
        # if v is None:
        #     break

        print("消费者%d消费了数据%d" % (id,v))
        time.sleep(1)
    # 任务完成
    q.task_done()

if __name__ == "__main__":

    #获取队列
    #栈【水杯】:先进后出,后进先出    队列【水平放置的水管】:先进先出,后进后出
    q = queue.Queue()

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

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

三、协程

#1.
# def a():
#     print(1)
#     b()
#     print(2)
#     b()
#     print(3)
#     b()
#
# def b():
#     print("x")
#     print("y")
#     print("z")
#
# a()
"""
1
x
y
z
2
x
y
z
3
x
y
z

"""

#2.
"""
子程序/函数:一个线程就是执行一个子程序,子程序调用只有一个入口,一次返回,调用的顺序是明确的

协程:看上去也是子程序,但是执行过程中,在子程序的内部可以中断,然后转去执行别的子程序,
但是,这种情况不是函数的调用
"""

def a():
    print(1)
    yield "x"
    print(2)
    yield "y"
    print(3)
    yield "z"

#协程的最简单的风格,控制函数的阶段执行,节约线程或者进程的切换
r = a()
#print(r)
print(next(r))
print(next(r))
print(next(r))
"""
1
x
2
y
3
z
"""

#与进程和线程相比,协程的执行效率较高,
# 因为只有一个进程,只有一个线程,也不存在同时修改变量的冲突,使用共享西苑不需要锁,直接使用

print("=" * 30)

#3.next()和send()可以触发yield的暂停
#next只能获取生成器中的数据,但是,send不但可以获取,还可以完成数据的传递
def run():
    data = ""
    r = yield  data
    print(1,r)
    r = yield "aaa"
    print(2, r)
    r = yield "bbb"
    print(3, r)
    r = yield "ccc"

m = run()
print(m)

print("结果1:",m.send(None))
print("结果2:",m.send("111"))
print("结果3:",m.send("222"))
print("结果4:",m.send("333"))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值