八、进程、线程和协程

  

  进程概念:进程,是计算机中的程序关于某数据集合上的一次运动活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。即执行中的程序是进程,例如qq不是进程,当qq运行的时候,就是一个进程

  程序并不能单独运行,只能将程序装载在内存中,系统为它分配资源才能运行,而这种执行的程序称为进程,程序和进程的区别在于:程序是指令的集合,它是进程进行的静态描述文本,进程是程序的一次执行活动,属于动态概念,在多道编程中,允许多个程序加载到内存中,在操作系统的调度下,可以实现并发执行,这样的设计,大大提高了cpu的利用率,进程的出现让每个用户感觉到自己独享cpu,因此,进程就是为了在cpu上实现多道编程而提出的

操作系统的作用:
    1.隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    2.管理、调度进程,并且将多个进程对硬件的竞争变得有序
  
多道技术:
    1.产生背景:针对单核,实现并发
    ps:
    现在的主机一般是多核,那么每个核都会利用多道技术
    有4个cpu,运行于cpu1的某个程序遇到IO阻塞,会等到io结束再重新调度,会被调度到4个cpu中的一个,具体由操作系统调度算法决定
    
    2.空间上的复用:如内存中同时有多道程序
    3.时间上的复用:复用一个cpu的时间片
    强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行

 

第一,进程是一个实体,每个进程都有自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region),文本区域存储处理器执行的代码,数据区域存储变量和进程执行期间使用的动态分配的内存,堆栈区域存储活动调用的指令和本地变量
第二,进程是一个‘执行中的程序’,程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行其),它才能成为一个活动的实体,称之其为进程
进程是操作系统中最基本、最重要的概念,是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序设计操作系统都建立在进程的基础上

从理论角度来看,是对正在运行的程序过程的抽象
从实现角度来看,是一种数据结构,目的在于清晰的刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序

动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单元,同时也是系统分配资源和调度的独立单位
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制快三部分组成
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果,但是在执行过程中,程序不能发生改变

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念,而进程是程序在处理机上的一次执行过程,它是一个动态的概念
程序可以作为一种软件资料长期存在,而进程是有一定的生命期的,程序是永久的,进程是暂时的

  进程调度

 作业调度是外存与内存之间的调度,发生频率很低,使进程从创建态到就绪态的过程,而进程调度是从内存到cpu的调度,发生频率很高,使进程从就绪态到运行态的过程。

  作业调度算法:1).先来先服务算法.2).短作业优先算法.3).高响应比优先算法
  进程调度算法:1).时间片轮转算法 2).优先级调度算法 3).多级反馈队列算法

  先来先服务调度算法:是一种最简单的调度算法,该算法即可用于作业调度,也可以用于进程调度,FCFS算法比较有利于长作业(进程),而不利于短作业(进程),由此可知,本算法适合于cpu繁忙型作业,而不利于I/O繁忙型的作业(进程)

  短作业优先调度算法:短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度,但对长作业不利,不能保证紧迫性作业(进程)被及时处理,作业的长短知识被估算出来的

  时间片轮转算法:待补

  多级反馈队列算法:待补

  程序的并行与并发

  并行:并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑(资源够用,比如三线程四核cpu)

  并发:并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核cpu资源)同时只能够一个人,A走一段后,让给B,B用完继续给A,交替使用,目的是提高效率

  区别:

  并行是从微观上,也就是一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器

  并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session

  同步异步阻塞非阻塞

  在程序的运行过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪、运行和阻塞

  就绪状态:当进程已分配到除cpu以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态

  执行/运行状态:当进程已获得处理机,其程序正在处理机上执行,此时的进程状态为执行状态

  阻塞:正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态,引起进程阻塞的时间可有多种,例如:等待I/O完成,申请缓冲区不能满足,等接信号等

#程序在开始运行之后,并不是立即开始执行,而是会进入就绪状态,等待操作系统调度开始执行
import time             #程序开始执行
print('程序开始执行')  #程序开始执行        
name = input('>>>')#在这里遇到等待用户输入操作,程序进入阻塞状态
#用户输入之后进程并不是立即执行,而是进入就绪状态,等待操作系统的调度继续执行
print(name)              #运行
time.sleep(1)            #阻塞
#就绪
print('程序结束运行’)    #运行
#结束

  同步和异步

  所谓同步就是一个任务的完成需要依赖另一个任务时,只有等待被依赖对象的任务完成后,依赖的任务才能完成,这种是一种可靠的任务序列,要么成功都成功,失败都失败,两个任务的状态可以保持一致。相当于客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情,当服务端做完了才返回到客户端,即收到服务器响应才能进行下一个请求。(单纯的银行排队,排到了,再进行下一个动作)

  所谓异步是不需要等待被依赖的任务完成,只是同时被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务也就算完成了,至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。相当于当客户端发送服务端请求时,在等待服务端响应的时候,不必一直等待服务端的响应,客户端可以做下一个动作,节约了时间,提高了效率,最终结果可能会由服务端通过回调函数返还给客户端(可以去做下一个动作,等着被叫号就好了,回调通知)

  阻塞和非阻塞(相对于线程)

  阻塞与非阻塞两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关,也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。

  阻塞调用是指调用结果返回前,当前线程会被挂起,函数只有在得到结果之后才会返回,阻塞调用与同步调用不同,对于同步调用来说,当前线程是激活的,只是从逻辑上当前函数没有返回而已。例如,从CSocket中调用Receive函数,如果缓冲区中没有数据,这个函数会一直等待,直到有数据才返回,此时,当前线程还会继续处理各种各样消息,如果主窗口和调用函数在同一个线程中,除非在特殊的界面操作函数中调用,其实主界面还是应该可以刷新;当socket工作在阻塞模式时,如果没有数据的情况下调用该函数,则当前线程会被挂起,直到有数据为止(全部写完才能读)

  非阻塞指在不能立刻得到结果钱,该函数不会阻塞当前线程,而会立即返回(采用写多少就读多少的策略)

  同步/异步 与 阻塞/非阻塞

  同步阻塞:效率最低(傻傻等待)

  异步阻塞:异步操作是可以被阻塞的,只不过它不是在处理消息时阻塞,而是在等待消息时阻塞(傻傻等待,会被叫)

  同步非阻塞:效率低下,(排队的过程中,一边看是否到了, 一边玩手机,程序在两种不同的行为中切换)

  异步非阻塞:效率更高(拿着纸条,可以去做别的,到了叫你)

  进程的创建与结束:

 

 

 

 

 

  线程概念:线程,有时被称为轻量级进程,是程序执行流的最小单元。可以理解为,线程是属于进程的,平时写的简单程序是单线程的,多线程和多线程的区别在于多线程可以同时处理多个任务。

  进程之间的内存独立,而属于同一个进程多个线程之间的内存是共享的,多个线程可以直接对他们所在进程的内存数据进行读写并在线程间进行交换。

  协程概念:协程是一种用户态的轻量级线程,如果说多进程对于多cpu,多线程对于多核cpu,那么事件驱动和协程则是在充分挖掘不断提高性能的单核cpu的潜力,既可以利用异步优势,又可以避免反复系统调用,还有进程切换造成的开销。协程也是单线程,但是它能让原来要使用异步+回调方式写的代码,可以用看似同步的方式写出来,他是实现推拉互动的所谓非抢占式协作的关键。对于python来说,由于python多线程中解释器导致的同时只能有一个线程访问cpu,所以对协程需求就相比其他语言更为紧迫。

  单核CPU实现多任务原理:操作系统轮流让各个任务交替执行,QQ执行2us,切换到微信,在执行2us,再切换到微博,执行2us……。表面是看,每个任务反复执行下去,但是CPU调度执行速度太快了,导致我们感觉就行所有任务都在同时执行一样

  多任务:操作系统同时可以运行多个任务

  多进程:多个任务,比如音乐,word等
  多线程:在进程内部做多件事,即多个子任务,比如word里的打字、打印等同时进行
  多任务实现的3种方式:
  多进程模式
  多线程模式
  多进程+多线程模式

  多核CPU实现多任务原理:真正的秉性执行多任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量,所以,操作西永也会自动把很多任务轮流调度到每个核心上执行 并发:看上去一起执行,任务书多于CPU核心数 并行:真正一起执行,任务数小于等于CPU核心数
  线程是最小的执行单元,进程由至少一个线程组成

   并发:看上去一起执行,任务数多于CPU核心数 
  并行:真正一起执行,任务数小于等于CPU核心数

  拥有一个多进程程序:

import multiprocessing #引入multiprocessing模块
import time
def func(msg): #要放在子进程执行的代码
    for i in range(s):
        print(msg)
        time.sleep(3)
if __name__ == '__main__':
    p = multiprocessing.Process(target = func,args = ('hello',))#创建一个新进程,将要放在子进程中执行的方法名和参数传给multiprocessing类
    p.start() #启动子进程
    p.join() #设置主进程阻塞,知道子进程执行完毕在继续完成
    print('have done')
#注意:必须在启动子进程前加上if__name__ == 'main'

  进程池:

def func(msg):
    print(msg,'*** in func')
    time.sleep(3)
if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 5)#初始化一个进程池,参数是5,表示这个进程池中将有5个进程
    for i in range(3):
        print(i)
        pool.apply_async(func,('hello %d'%(i), ))#为进程池中的进程注入func方法,这里有apply_async和apply两种方式,分别表示‘同步’和'异步'
        #pool.apply(func,('hello %d'%(i), ))
    pool.close() #关闭进程池,至此,进程池不再有进程可以接受任务
    #pool.terminate() #结束工作进程,即结束当前进程池中的所有进程,不再处理未完成的任务
    pool.join()#主进程阻塞,等待子进程执行完毕,再继续执行主进程,等待子进程的退出,join方法要在close或terminame之后使用
    print('have dong')
#teminame和join是一对方法,表示的内容截然相反,两个方法都必须在close方法之后执行,当然也可以不执行这两个方法,那么子进程和主进程就各自执行各自的,无论执行到那里,子进程会随着主进程的结束而结束

  获取进程池中进程的执行结果

import multiprocessing
import time
def func(msg):
    print('msg:',msg)
    time.sleep(3)
    print('end')
    return('multi_result:'+msg)
if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 4)
    result = []
    for i in xrange(3):
        msg = 'hello %d'%(i)
        multi_result = pool.apply_async(func,(msg, ))
        result.append(multi_result)
        pool.close()
        pool.join()
        for res in result:
            print(res.get())
        print('have done')

  进程之间的内存共享
  每个进程都拥有自己的内存空间,进程间的内存是无法共享的
  但是python提供了方法让程序的子进程之间实现简单的数据共享,一个是Array数组,一个是multiprocessing模块中的Manager类,需要注意的是,Array数组大小必须固定,Manager需要在linux系统下运行

from multiprocessing import Process,Array
temp = Array('i',[11,22,33,44])
def Foo(i):
    temp[i] = 100+i
    for item in temp:
        print(i,'---------->',item)
for i in range(2):
    p = Precess(target = Foo,args = (i,))
    p.start

  多进程

  unix/linux操作系统提供了一个fork()系统调用,它非常特殊,普通函数的调用,调用一次返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(父进程)复制了一份(子进程),然后,分别在父进程和子进程内返回
  子进程永远返回0,而父进程返回子进程的ID,这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID

  

  在UNIX里,除了进程0(即PID=0的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。

  操作系统内核以进程标识符(Process Identifier,即PID)来识别进程。进程0是系统引导时创建的一个特殊进程,在其调用fork创建出一个子进程(即PID=1的进程1,又称init)后,进程0就转为交换进程(有时也被称为空闲进程),而进程1(init进程)就是系统里其他所有进程的祖先。


  python的os模块封装了常见系统调用,其中就包括fork,可以在python程序中创建子进程

import os
print('Process (%s) start...'%os.getpid())
#only works on unix/linux/mac
pid = os.fork()#父进程 #使用os.fork()之后,在内存中把进程的代码及内存分配情况拷贝一份生成子程序的运行空间,这样子进程的所有代码都与父进程一样,两个进程之间的运行是独立的,互不影响
if pid == 0:#在父进程中获取到的pid号是子进程的pid号,在子进程中获取的pid是0
    print('I an child process (%s) and my parent is %s'%(os.getpid(),os.getppid())) #子任务  父任务
else:
    print('I (%s) just created a child process (%s)'%(os.getpid(),pid))#获取本次父id,此时创建了子进程,pid变为了0

# 当前进程号 os.getpid() # 父进程号 os.getppid() 
Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.

  multiprocessing

  process模块介绍:process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建

  编写多进程的服务程序,unix/linux是正确的选择,windows没有fork调用,multiprocessing模块是跨平台版本的多进程模块

  在multiprocessing中,通过创建一个process对象然后调用它的start()来生成进程,process和threading.Thread API相同

#单任务现象
from time import sleep
def run():
    while True:
        print('sunck is a nice man')
        sleep(1.2)
if __name__ == '__main__':
    while True:
        print('sunck is a good man')
        sleep(1)
    run()
#不会执行到run方法,只有上面的while循环结束才可以执行,这就是单个进程的局限性,从上往下挨着顺序执行碰到死循环下面的程序就不会执行

 

  启动进程实现多任务(同时执行任务与1任务2)

def f(name):
    print('hello', name)
    time.sleep(1) #即使没有这个也是先执行父进程
    print('我是子进程')
if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    #p.join()
    print('我是父进程')

 

我是父进程
hello bob
我是子进程

  

import time
from multiprocessing import Process
import os
def run(name):
  while True:
        time.sleep(2)
        print("子进程ID号:%d,run:%s" % (os.getpid(), name))  # os.getpid()进程ID
if __name__ == "__main__":
    print("父进程启动:%d" % os.getpid())
    # 创建子进程
    p = Process(target=run, args=("Ail",))  # target表示调用对象,即子进程要执行的任务,;args传参数(元组),必须要有逗号
    p.start()   # 启动进程
    while True:
        print("死循环")
        time.sleep(1)

#p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况,如果p还保存了一个锁,那么也将不会被释放,从而导致死锁
#p.is_alive():如果p任然运行,返回True
#p.join([timeout]):主线程等待p终止(强调:主线程处于等的状态,而p是处于运行的状态),timeout是可选的超时时间,需要强调的是,p.join()只能join住start开启的进程,而不能join住run开启的进程
#p.name:进程的名字
#p.pid:进程的pid
#p.daemon:默认值False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前创建

#在windows操作系统中,由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动import启动它的这个文件,而在import的时候又执行了整个文件,因此将process()直接写在文件中就会无限递归创建子进程报错,
#所以必须把创建子进程的部分使用if__name__ == '__main__'判断保护起来,import的时候,就不会递归运行了

 

父进程启动:10084
死循环 #因为先执行了一次死循环之后才阻塞的
死循环
死循环
子进程ID号:3284,run:Ail
死循环
死循环
子进程ID号:3284,run:Ail
死循环
死循环
子进程ID号:3284,run:Ail
死循环
死循环
子进程ID号:3284,run:Ail

  

 

 

 

from multiprocessing import Process
from time import sleep
import os
def run(str):
    while True:
        #os.getpid()获取当前进程id号
        #os.getppid()获取当前进程的父进程id号
        print("sunck is a %s man--%s--%s"%(str,'当前进程号:%s' % os.getpid(), '父进程号:%s' % os.getppid()))
        sleep(5)

if __name__ == "__main__":
    print("主/父进程启动--%s"%(os.getpid()))
    #创建子进程
    #target说明进程执行的任务(任务1)
    #args需要传递的参数
  p = Process(target=run, args=["handsome"])   # 创建子进程, 执行函数run, 传入参数handsome, 注意元组里面只有一个元素的时候必须加逗号
    #启动进程
    p.start()
    while True: #任务2
        print("sunck is a good man")
        sleep(5) #在睡觉的时候就去做别的了
主/父进程启动--11536
sunck is a good man
sunck is a handsome man--当前进程号:11676--父进程号:11536
sunck is a good man
sunck is a handsome man--当前进程号:11676--父进程号:11536
......
************************************************************
当if__name__ == '__main__'中的sleep(1)时,结果为
主/父进程启动--10072
sunck is a good man
sunck is a handsome man--当前进程号:8264--父进程号:10072
sunck is a good man
sunck is a good man
sunck is a good man
sunck is a good man
sunck is a good man
sunck is a handsome man--当前进程号:8264--父进程号:10072
sunck is a good man
......

  

  

 

 

  进程间的执行顺序

import time
from multiprocessing import Process
import os 
def run():
    print('子进程开启')
    time.sleep(2)
    print('子进程结束')

if __name__ == '__main__':
    print('父进程启动')
    p = Process(target = run)
    p.start()
    print('父进程结束')

 

父进程启动
父进程结束
子进程开启
子进程结束

  加入join()方法:父进程的结束不能影响子进程,让父进程等待子进程结束,在执行p.join(),让父进程阻塞在这里,等待子进程结束后再执行以下语句

import time
from multiprocessing import Process
import os 
def run():
    print('子进程开启')
    time.sleep(2)
    print('子进程结束')
if __name__ == '__main__':
  print('父进程启动')
    p = Process(target = run)
    p.start()
    p.join() 
    print('父进程结束')
父进程启动
子进程开启
子进程结束
父进程结束

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

import time
from multiprocessing import Process
import os 
num = 10
def run():
    print('子进程开始')
    global num
    num += 1
    print('子进程num:%d'%num)
if __name__ == '__main__':
    print('父进程开始')
    p = Process(target = run)
    p.start()
    p.join()
    #在子进程中改变全局变量对父进程中的全局变量没有影响
    print('父进程结束,num:%s'%num)
父进程开始
子进程开始
子进程num:101
子进程结束
父进程结束。num:100

  守护进程

  会随着父进程的结束而结束,父进程创建守护进程:

    其一:守护进程会在主进程代码执行结束后就终止

    其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

  注意:进程之间是相互独立的,主进程代码运行结束,守护进程随即终止

#守护进程
#守护子进程
from multiprocessing import Process
import os,time,random
def task():
    print('%s is running'%os.getpid())
    time.sleep(2)
    print('%s is done'%os.getpid())
#守护进程内无法再开启子进程,否则抛出异常
if __name__ == '__main__':
    p=Process(target=task)
    p.daemon = True #1、必须在p.start()之前
    p.start()
    print('')
#结果:主
#原因是:主进程程序启动执行到p子进程,由于子进程需要开辟内存空间,由于需要好费时间,所以主进程会首先输出’主‘,
#由于主进程执行完毕,那么守护子进程也被干掉了,随之主进程就退出了

if __name__ == '__main__':
    p = Process(target = task)
    p.daemon = True #必须在p.start()之前
    p.start()
    p.join()
    print('')
#结果:
# 4756 is running
#4756 is done
#
#join起到了阻塞的作用,子进程执行完毕,才执行主进程,所以加上join
#执行到join,由于主进程print('主')没有执行完,所以守护进程不会被干掉,继续执行


#守护子进程、非守护子进程并存
from multiprocessing import Process
from threading import Thread
import time,os
def foo():
    print(123)
    time.sleep(1)
    print('end123')
def bar():
    print(456)
    time.sleep(3)
    print('end456')
if __name__ == '__main__':
    p1 = Process(target = foo)
    p2 = Process(target = bar)
    p1.daemon = True
    p1.start()
    p2.start()
    print('main...')
#结果:
#main...
#456
#end456
#由于p1,p2都是子进程,需要开辟内存空间,需要耗费时间,所以会优先输出主进程'main',由于p1是守护子进程,p2是非守护子进程,
#当主进程执行完毕,p1守护进程就退了,但是还有一个p2非守护进程,所以p2会执行自己的代码,当p2执行完毕,那么主进程也就退了,进而整个程序就退了



#守护子线程
#无论是进程还是线程,都遵循:守护**会等待主**运行完毕后被销毁
#需要强调的是:运行完毕并非终止运行
#对主进程来说,运行完毕指的是主进程代码运行完毕
#对主线程来说,运行完毕指的是主线程所在的线程内所有非守护线程统统运行完毕,主线程才算运行完毕
#详细解释
#主进程在其代码结束后就已经算运行完毕了(守护进程在此时被回收),然后主进程会一直等待非守护的子进程都运行完毕的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束
#主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时被回收),因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束
from multiprocessing import Process
from threading import Thread
import os,time,random
def task():
    print('%s is running'%os.getpid())
    time.sleep(2)
    print('%s is done'%os.getpid())
if __name__ == '__main__':
    t = Thread(target = task)
    t.daemon = True
    t.start()
    print('')
#结果:
#13368 is running
#
#原因是:在执行到子进程t,由于主线程子线程通用一块内存,所以不存在不同进程创建各自空间,所以就先输出子进程的执行任务代码(子进程的执行顺序是挨着的),所以输出print(‘%s is running’ %os.getpid())
#由于time.sleep(2),所以就会执行主线程‘主’,然后主线程执行完毕,那么即使2秒后,由于主线程执行完毕,那么子守护线程也就退出了,所以print(‘%s is done’ %os.getpid())就不会执行了


#守护子进程非守护子进程并存
from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print('end123')
def bar():
    print(456)
    time.sleep(3)
    print('end456')
if __name__ == '__main__':
    t1 = Thread(target = foo)
    t2 = Thread(target = bar)
    t1.daemon = True
    t2.start()
    t1.start()
    print('main...')
#结果:
#456
#123
#mian...

#end123

#end456
#t1是守护子线程,t2是非守护子线程,跟主线程使用一块内存,所以会输出t1,t1子线程的代码,所以执行456,123由于t1,t2都有睡眠时间,所以,
#执行主线程代码,然会对主线程来说,运行完毕指的是主线程所在的进程内所有的非守护线程统统运行完毕,主线程才算运行完毕,所以会执行t1,t2睡眠后的任务代码,
#然后程序退出,为何t1守护子进程也会执行sleep后的代码,要注意,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,当时t2还没运行完

 

  

  socket聊天并发实例:待补

 

  


  multiprocessing调用类函数与多进程中的其他方法

#multiprocessing调用类函数与其他方法
from multiprocessing import Process
import time
import random
class Myprocess():
    def __init__(self,person):
        self.name = person
        super().__init__()
    def run(self):
        print('%s正在和网红脸聊天'%self.name)
        time.sleep(random.randrange(1,5))
        print('%s还在和网红脸聊天'%self.name)
if __name__ == "__main__":
    p1 = Myprocess('葫芦娃')
    p2 = Process(target=p1.run)
    p2.start()
    print(p2.pid)
#结果:
#5056
#葫芦娃正在和网红脸聊天
#葫芦娃还在和网红脸聊天
#若加上以下代码 
p2.terminate() # 关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活 
print(p2.is_alive()) # 结果为True 
print('开始') 
print(p2.is_alive()) # 结果为False 
#结果: 
#True 
#开始 
#False

 

 

 

  进程同步(multiprocessing.Lock)

  锁(multiprocessing.Lock) 

  让多个任务同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启不受控制,尽管并发编程让我们能更加充分的利用IO资源,但是当多个进程使用同一份数据资源时,会引发数据安全或顺序混乱的问题

  加锁可以保证多个进程修改同一个数据时,同一时间只能有一个任务可以进行修改,即串行的修改,速度变慢,牺牲了速度却保证了数据安全

  虽然可以用文件共享数据实现进程间通信,但是:1).效率低(共享数据基于文件,而文件是硬盘上的数据)          2).需要自己进行加锁处理 

  可以通过multiprocessing模块提供的基于消息的IPC通信机制:队列和管道,兼顾效率高(多个进程共享一块内存的数据),处理好锁的问题

  队列和管道都是将数据存放于内存中,队列又是基于管道+锁实现的,可以从复杂的锁问题中解脱出来,应该尽量避免使用共享数据,既可能使用消息传递和队列,避免处理复杂的同步和锁的问题,而且在进程数目增多时,往往可以获得更多的可获展性

 

 

  进程间通信--队列(multiprocessing.Queue)

  进程间通信IPC(Inter-Process Communication)

  队列:创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递

#multiprocessing模块支持进程间通信的两种主要形式:管道和队列,都是基于消息传递实现的
from multiprocessing import Queue
q = Queue(3) #创建共享的进程队列,数字是队列中允许的最大项数,如果省略此参数,则无大小限制,底层队列使用管道和锁定实现,另外,还需要运行支持线程一遍队列中的数据传输到底层管道中
#put,get,put_nowait,get_nowait,full,empty
q.put(3) #q.put(item,block=True,timeout=None)将item放入队列,如果队列已满,此方法将阻塞至有空间可用为止,block控制阻塞行为,默认为True,timeout指定在阻塞模式中等待可用空间的时间长短,超出后将引发Queue.Full异常
q.put(3)
q.put(3) #如果队列已满,程序就会停在这里,等待数据被别人取走,再将数据放入队列,如果数据一直不取走,程序会永远停在这里
try:
    q.put_nowawit(3) #可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错
except: #因此可以用一个try语句来处理这个错误,这样程序不会一直阻塞下去,但是会丢掉这个消息
    print('队列已经满了')
#因此,我们在放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了
print(q.full()) #满了,判断队列是否满了
print(q.get())
print(q.get())
print(q.get()) #同put方法一样,如果队列已经空了,那么继续取就会出现阻塞
try:
    q.get_nowait(3) #可以使用get_nowait,如果队列满了就不会阻塞,但是会因为没取到值而报错
except: #因此可以用一个try语句来处理这个错误,这样程序不会一直阻塞下去
    print('队列已经空了')
print(q.empty())#队列是否已满
队列已经满了
True
3
3
3
队列已经空了
True

  上述例子还没有加入进程通信,只是先来看看队列为我们提供的方法,以及这些方法的使用和现象

  队列:队列可以并发的排多个线程,对排序的线程处理,并且每个需要处理线程只需要将请求的数据放入队列容器的内存中,线程不需要等待,当排列完毕处理完数据后,线程再准时来取数据即可,请求数据的线程只与这个队列容器存在关系,处理数据的线程down掉不会影响到请求数据的线程,队列会派给其他的线程处理这部分数据,实现了解耦(修改一个函数,不会有串联关系),提高效率,队列内会有一个有顺序的容器,列表与这个容器是有区别的,列表中的数据虽然是排序的,但数据被取走后还会保留,而队列中这个容器的数据被取走后不会保留,当必须在多个线程之间安全的交换信息时,队列在线程编程中非常有用

  FIFO(first in first out)先进先出

from queue import Queue,LifoQueue,PriorityQueue
q = Queue(maxsize = 0) #maxsize设置队列中,数据上线,小于或等于0则不限制,容器中大于这个数则阻塞,直到队列中的数据被消掉
q.put(0) #写入队列数据
q.put(1)
q.put(2)
print(q.queue) #输出当前队列所有数据
q.get() #删除队列数据并返回该数据
print(q.queue)
deque([0, 1, 2])
deque([1, 2])

  LIFO(last in first out)后进先出

#LIFO即Last in First Out,后进先出。与栈的类似,使用也很简单,maxsize用法同上
lq = LifoQueue(maxsize=0)
lq.put(0)#队列写入数据
lq.put(1)
lq.put(2)
print(lq.queue)#输出队列所有数据
lq.get()#删除队尾数据,并返回该数据
print(lq.queue)#输出队列所有数据
[0, 1, 2]
[0, 1]

  
  PriorityQueue:优先队列,级别越低,越优先,即越先出去

# 存储数据时可设置优先级的队列
# 优先级设置数越小等级越高
pq = PriorityQueue(maxsize=0)
pq.put((9,'a'))#写入队列,设置优先级
pq.put((7,'c'))
pq.put((1,'d'))
print(pq.queue)#输出队例全部数据
pq.get()#取队例数据,可以看到,是按优先级取的。
pq.get()
print(pq.queue)
[(9, 'a')]

  deque:双边队列

#双边队列
dq = deque(['a','b'])
dq.append('c')#增加数据到队尾
dq.appendleft('d')#增加数据到队左
print(dq)#输出队列所有数据
print(dq.pop())#移除队尾,并返回
print(dq.popleft())#移除队左,并返回
deque(['d', 'a', 'b', 'c'])
c
d

  生产者消费者模型

  在并发编程中使用生产者和消费者模式能解决大多数并发问题,该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度

  为什么要使用生产者和消费者模式:在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程,在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据,同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者,于是引入生产者和消费者模型

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

  一个生产者和一个消费者

  1)q.put(None):生产者给放一个None进去

# 生产者与消费者1
from multiprocessing import Process,Queue
import os
import time
import random
#首先得有生产者和消费者
# 生产者制造包子
'''这种用 q.put(None)放进去一个None的方法虽然解决了问题
但是如果有多个生产者多个消费者,或许框里面没有包子了但是
还有其他的食物呢,你就已经显示空着,这样也可以解决,就是不完美,
还可以用到JoinableQueue去解决'''
def producter(q):
    for i in range(10):
        time.sleep(2) #生产包子得有个过程,就先让睡一会 #只删除了这条语句结果就变成了制造,制造,制造,制造,制造...吃,吃,吃...
        res = '包子%s'%i #生产了这么多的包子
        q.put(res)  #把生产出来的包子放进框里面去
        print('\033[44m%s制造了%s\033[0m'%(os.getpid(),res))
    q.put(None) #只有生产者才知道什么时候就生产完了(放一个None进去说明此时已经生产完了)
# 消费者吃包子
def consumer(q):
    while True:#假如消费者不断的吃
        res = q.get()
        if res is None:break #如果吃的时候框里面已经空了,就直接break了
        time.sleep(random.randint(1,3)) #只删除这条语句,结果变成了制造,吃,制造,吃,制造,吃,制造,吃...
        print('\033[41m%s吃了%s\033[0m' % (os.getpid(),res))
if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producter,args=(q,))
    p2 = Process(target=consumer,args=(q,))
    p1.start() #进程当第一个开始后,之后因为有sleep所以先后顺序有所混乱
    p2.start()
    p1.join()
    p2.join()  #等待执行完上面的进程,再去执行主
    print('')

 

10964制造了包子0
5344吃了包子0
10964制造了包子1
5344吃了包子1
10964制造了包子2
10964制造了包子3
5344吃了包子2
10964制造了包子4
5344吃了包子3
5344吃了包子4
主

  2)利用JoinableQueue

# 生产者和消费者2
from multiprocessing import Process,JoinableQueue
import os
import time
import random
#首先得有生产者和消费者
# 消费者吃包子
def consumer(q):
    '''消费者'''
    while True:#假如消费者不断的吃
        res = q.get()
        time.sleep(random.randint(1,3))
        print('\033[41m%s吃了%s\033[0m' % (os.getpid(),res))
        q.task_done() #任务结束了(消费者告诉生产者,我已经把东西取走了)
# 生产者制造包子
def producter(q):
    '''生产者'''
    for i in range(5):
        time.sleep(2) #生产包子得有个过程,就先让睡一会
        res = '包子%s'%i #生产了这么多的包子
        q.put(res)  #把生产出来的包子放进框里面去
        print('\033[44m%s制造了%s\033[0m'%(os.getpid(),res))
    q.join()
 
if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producter,args=(q,))
    p2 = Process(target=consumer,args=(q,))
    p2.daemon = True #在启动之前把消费者设置成守护进程,p1结束了p2也就结束了,即主进程结束p2就退了,因为先执行p1.start(),后执行p1.start(),再执行p2.start(),当主进程全部运行完了,所以守护进程p2就不执行了
    p1.start()
    p2.start()
    p1.join() #在等生产者结束(生产者结束后,就不制造包子了,那消费者一直在吃,就卡住了
    #都不生产了还吃啥,就把消费者也结束了  )
      #等待执行完上面的进程,在去执行主
    print('') #主进程

 

10776制造了包子0
6252吃了包子0
10776制造了包子1
10776制造了包子2
6252吃了包子1
10776制造了包子3
10776制造了包子4
6252吃了包子2
6252吃了包子3
6252吃了包子4
主

  多个生产者和多个消费者

  1)q.put(None):生产者放一个None进去

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
 
def producer(name,q):
    for i in range(2): #因为这里生产者的每个进程循环了两次,所以有六个生产者,有会有六个消费者
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
 
 
 
if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))
 
    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))
 
    #开始
    p1.start()
    p2.start()
    p3.start()
    c1.start()
  c2.start()
    p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号
    p2.join()
    p3.join()
    q.put(None) #有几个消费者就应该发送几次结束信号None
    q.put(None) #发送结束信号
    print('')

 

5176 生产了 骨头0
9080 生产了 包子0
5176 生产了 骨头1
7800 生产了 泔水0
5332 吃 骨头0
9080 生产了 包子1
7800 生产了 泔水1
主
5332 吃 包子0
5332 吃 骨头1
5332 吃 泔水0
5332 吃 包子1
5332 吃 泔水1
为什么主在中间呢??????????????????????、因为没有c1.join和c2.join

  2)利用JoinableQueue

from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
        q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了

def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
    q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。


if __name__ == '__main__':
    q=JoinableQueue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))
    c1.daemon=True
    c2.daemon=True

    #开始
    p_l=[p1,p2,p3,c1,c2]
    for p in p_l:
        p.start()

    p1.join()
    p2.join()
    p3.join()
    print('') 
    
    #主进程等--->p1,p2,p3等---->c1,c2
    #p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
    #因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。
9552 生产了 泔水0
6676 吃 泔水0
9552 生产了 泔水1
8352 生产了 骨头0
8220 生产了 包子0
8352 生产了 骨头1
9492 吃 泔水1
6676 吃 骨头0
8220 生产了 包子1
9492 吃 包子0
6676 吃 骨头1
9492 吃 包子1
主

  

 

 

 

 

 

 

 

 

  进程池pool

  如果要启动大量的子进程,可以用进程池的方式批量创建子进程。在使用python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量时间,如果操作对象数目不大,可以直接使用Process类动态生成多个进程,如果过多,用手动限制进程数量就显得很繁琐,此时需要进程池

  pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool时,如果池还没有满,就会创建一个新的进程来执行请求,等到处理完毕,进程并不关闭,将进程放回池中继续等待任务,如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求,也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在执行,这样不会增加操作系统的调度难度,还节省了关闭进程的时间,也一定程度上能够实现并发效果。

import random
from multiprocessing.pool import Pool
from time import sleep,time
import os 
def run(name):
    print('%s子进程开始,开始ID:0.2%d'%(name,os.getpid()))
    start = time()
    sleep(random.choice([1,2,3,4])) #随机选择1,2,3,4秒进行睡眠
    end = time()
    print('%s子进程结束,进程ID:%d,耗时%f'%(name,os.getpid(),end - start))

if __name__ == '__main__':
    print('父进程开始')
    #创建多个进程,表示可以同时执行的进程数量,默认大小是cpu的核心数
    p = Pool(4) #允许进程池同时放入4个进程
    for i in range(5): #启动了,但是还没被允许,因为同一时间只有2个在运行
        #创建进程,放入进程池统一管理,并行
        p.apply_async(run,args=(i,))
    #如果我们用的是进程池,在调用join()之前必须要先close(),并且在close()之后不能再继续往进程池中添加新的进程
    p.close() #注意:此处未写p.start(),关闭进程池,关闭后p不在接受新的请求,在join()之前先要关闭进程池,避免添加新的进程
    #进程池对象调用join,会等待进程池中所有的子进程结束完毕再去结束父进程,此方法只能在close()和teminate()之后调用
    p.join()
    print('父进程结束') #批量备份完成后,往数据库写日志,但是父程序先写,因为父进程里写日志只连一次,子进程里每次都要连,因此次数是apply_async,他是非阻塞且支持结果返回进行回调
#发现一共执行的只有4个进程,代码要执行时,先看进程池满了没有,如果满了,就等待进程池里面进程执行完之后,再去执行下个进程
父进程开始
0子进程开始,开始ID:0.22592
1子进程开始,开始ID:0.27236
2子进程开始,开始ID:0.210200
3子进程开始,开始ID:0.27832
0子进程结束,进程ID:2592,耗时1.000057
4子进程开始,开始ID:0.22592
4子进程结束,进程ID:2592,耗时1.000057
1子进程结束,进程ID:7236,耗时2.000114
3子进程结束,进程ID:7832,耗时3.049174
2子进程结束,进程ID:10200,耗时4.000229
父进程结束

  对pool对象调用join()方法会等待所有的子程序执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了,注意子进程0,1,2,3是立即执行的,而子进程4要等待前面某个子进程完成后才执行,因为pool的默认大小在此电脑上是4,因此,最多同时执行4个进程,如果改成pool(5),由于pool的默认大小是cpu的核数,所以四个之后要等待结束后再进行。

import os
import time
import random
from multiprocessing import Pool
def work(n):
    print('%s run'%os.getpid())
    time.sleep(random.random())
    return n**2 #为什么会变成这样呢,经过实验,就是因为这个return,如果这个改成print,那么结果就和之前的一样了????会不会return不被算到进程里面??????但是改为print后,怎么会多了五个None
if __name__ == '__main__':
    p = Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
    res_1 = []
    for i in range(5):
        res = p.apply_async(work,args=(i,)) #异步运行,根据进程池中有的进程数,每次最多3个子进在异步执行,返回结果后,将结果放入列表中
    #归还进程,之后再执行新的任务,需要注意的是,进程池中的三个进程不会同时开启或者同时结束,而是执行完一个就释放一个进程,这个进程去接受新任务(应该是满了的情况吧?)
        res_1.append(res)
    #异步apply_async用法:如果使用异步提交的任务,主进程需要使用join,等待进程池内任务都处理完,然后可以用get收集结果
    #否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
    p.close() #不是关闭进程池,而是结束进程池接收任务,确保没有新任务再提交过来。
    p.join()#感知进程池中的任务已经执行结束,只有当没有新的任务添加进来的时候,才能感知到任务结束了,所以在join之前必须加上close方法
    for res in res_1:
        print(res.get())#使用get来获取apply_aync结果,如果是apply,则没有get方法,因为apply是同步执行,立即获得结果,也无需get
 #如果直接用res这个结果对象调用get方法获取结果的话,这个程序就变成了同步,因为get方法直接就在这里等着你创建的进程的结果,第一个进程创建了,并且去执行了,那么get就会等着第一个进程的结果,没有结果就一直等着,那么主进程的for循环是无法继续的,所以你会发现变成了同步的效果

 

10788 run
10308 run
824 run
10788 run
10308 run
0
1
4
9
16

  回调函数

  需要回调函数的场景:进程池中任何一个任务一旦处理完成,就立即告知主进程,我好了,可以处理我的结果,主进程则调用一个函数去处理该结果,该函数即回调函数

  可以把耗时间(阻塞)的任务放在进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果

from multiprocessing import Pool
def func1(n):
    print('in func1')
    return n*n
def func2(m):
    print('in func2')
    print(m*m)
if __name__ == '__main__':
    pool = Pool(5)
    pool.apply_async(func1,args = (10,))
    pool.close()
    pool.join()
in func1
from multiprocessing import Pool
def func1(n):
    print('in func1')
    return n*n
def func2(m):
    print('in func2')
    print(m)
if __name__ == '__main__':
    pool = Pool(5)
    pool.apply_async(func1,args=(10,),callback = func2)
    pool.close()
    pool.join()
in func1
in func2
100

  在此处,回调函数的作用就是将函数func1的返回值(return)传给func2,并执行func2函数,所以不能再pool.apply_async里面单独给func2传值,func2接受的参数就是func1的返回值

  判断func2在子进程还是主进程

from multiprocessing import Pool
import os
def func1(n):
    print('in func1',os.getpid())
    return n*n
def func2(m):
    print('in func2',os.getpid())
if __name__ == '__main__':
    print('主进程:',os.getpid())
    pool = Pool(5)
    pool.apply_async(func1,args=(10,),callback=func2)
    pool.close()
    pool.join()
主进程: 9292
in func1 11016
in func2 9292

  func2在主进程里,所以回调函数就是回到主进程继续执行

  如果在主进程中等待进程池中所有的任务都执行完毕后,再统一处理结果,则无需回调函数

from multiprocessing import Pool
import time,random,os

def work(n):
    time.sleep(1)
    return n**2
if __name__ == '__main__':
    p=Pool()

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

    p.close()
    p.join() #等待进程池中所有进程执行完毕

    nums=[]
    for res in res_l:
        nums.append(res.get()) #拿到所有结果
    print(nums) #主进程拿到所有的处理结果,可以在主进程中进行统一进行处理

 

,

  

 

 

  进程间的通信

  两个进程:一个负责写,一个负责读,当写的程序写完了某部分之后把数据交给读的进程进行使用,此时用到了multiprocessing模块中的Queue(队列),write()将写完的数据交给队列,再由队列交给read()

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

#写数据进程执行的代码
def write(q):
    print('Process to write: %s'%os.getpid())
    for value in ['A','B','C']:
        print('Put %s to queue...'%value)
        q.put(value) #写入队列
        time.sleep(random.random())

#读数据进程执行的代码
def read(q):
    print('Process to read: %s'%os.getpid())
    while True:#阻塞,等待获取write值
        value = q.get(True)
        print('get %s from queue'%value) #不会执行

if __name__ == '__main__':
    #父进程创建Queue,并传给各个子程序
    q = Queue()
    pw = Process(target = write, args = (q,))
    pr = Process(target = read,args = (q,))
    #启动子程序pw,写入
    pw.start()
    #启动子程序pr,读取
    pr.start()
    #等待pw结束
    pw.join()
    #pr进程里是死循环,无法等待其结束,只能强行停止(写进程结束了,所以读进程也可以结束了)
    pr.terminate()
Process to write: 50563
Put A to queue...
Process to read: 50564
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

  在unix/Linux下,可以使用fork()调用实现多线程
  要实现跨平台的多进程,可以使用multiprocessing模块
  进程间通信是通过Queue、Pipes等实现的(队列,管道)

  

 

 

  线程

  进程的缺点:1)进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了

        2)进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使线程中有那些不依赖于输入的数据,也将无法执行

  例如:上课的过程就是一个进程,需要听课、做笔记、思考才能完成听课的任务,如果只有进程,那么这三件事不能同时进行,听课的时候不能做笔记、思考等等,通过引入线程来让听课、做笔记、思考三个独立的过程并行起来。

  进程是资源分配的最小单位,线程是cpu调度的最小单位,每一个进程至少有一个线程。在一个进程内部,要同时做多件事,即多个‘子任务’,称为线程,一个进程至少有一个线程,python的线程是真正的Posix Thread,而不是模拟出来的线程,多线程的执行几乎是同步的,并且共享内存,但是它会对内存产生资源抢占的情况

  线程与进程的区别:

  1)地址空间和其他资源:进程间相互独立,同一进程的各线程间共享,某进程内的线程在其他进程不可见

  2)通信:进程间通信IPC,线程间可以直接读写数据段(如全局变量)来进行通信--需要进程同步和互斥手段的辅助,以保证数据的一致性,互斥锁:防止多个线程同时读写某一块内存区域。

  3)调度和切换:线程的上下文切换比进程上下文切换要快的多

  4)在多线程操作系统中,进程不是一个可执行的实体

  线程的特点

  在多线程的操作系统中,通常是在一个进程中包含多个线程,每个线程都是作为利用cpu的基本单元,是花费最小开销的实体,线程具有以下属性:

  1)轻型实体

   线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源

  线程的实体包括程序、数据和TCB,线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述

TCB包括以下信息:
1)线程状态
2)当线程不运行时,被保存的现场资源
3)一组执行堆栈
4)存放每个线程的局部变量主存区
5)访问同一个进程中的主存和其他资源
用于指示被执行指令序列的程序计数器、保留局部变量。少数状态参数和返回地址等的一组寄存器和堆栈

  2)独立调度和分派的基本单元

  在多线程os中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单元,由于进程很‘轻’,故线程的切换非常迅速且开销小(在同一个进程中)

  3)共享进程资源

  线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,首先表现在:所有线程都具有相同的进程ID,这意味着,线程可以访问该进程的每一个内存资源,此外,还可以访问进程所拥有的已打开文件、定时器、信号量(发现锁了,就在外面等)机构等,由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核

  4)可并发执行

  在一个进程中的多个线程之间,可以并发执行(并发是多个线程在一个cpu上快速的交替使用,并行是多个线程在多个cpu上同时使用),甚至允许在一个进程中所有线程都能并发执行,同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力

  全局解释器锁GIL??????????????????????

  python代码的执行由python虚拟机(解释器主循环)来控制,python在设计之初就考虑到要在主循环中,同时只有一个线程在执行,虽然python解释器可以运行多个线程,但是在任一时刻只有一个线程在解释器执行

  对python虚拟机的访问由全局解释器锁(GIL)控制,正是这个锁保证同一时刻只有一个线程在运行

  在多线程环境中,python虚拟机按以下方式执行:

  a、设置GIL

  b、切换到一个线程去运行

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用time.sleep(0))  

  d、把线程设置为睡眠状态

  e、再次重复以上所有步骤

  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

 

  启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()调用

#单线程
import time
begintime = time.time()
for a in range(10):
    print(a)
    time.sleep(1)
endtime = time.time() - begintime
print(endtime)
1 2 3 4 5 6 7 8 9 
10.014572858810425
from threading import Thread
import time
class Sayhi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        time.sleep(2)
        print('%s say hello' % self.name)
if __name__ == '__main__':
    t = Sayhi('egon')
    t.start()
    print('主线程')
主线程
egon say hello

 

#多线程
import threading
import time
def printNum(a):
    print(a)
    time.sleep(1)
begintime = time.time()
for a in range(10):
    t = threading.Thread(target = printNum,args = (a,))
    t.start()
endtime = time.time()-begintime
print(endtime)
0123456789(竖着的)
0.0030002593994140625
from threading import Thread
from multiprocessing import Process
import os
def work():
    print('hello',os.getpid())
if __name__ == '__main__':
    #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1=Thread(target=work)
    t2=Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid',os.getpid())

    #part2:开多个进程,每个进程都有不同的pid
    p1=Process(target=work)
    p2=Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid',os.getpid())
hello 7852
hello 7852
主线程/主进程pid 7852
主线程/主进程pid 7852
hello 2256
hello 1048

 

from threading import Thread
from multiprocessing import Process
import os
def work():
    print('hello')
if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')
#结果:
# hello
#主线程/主进程

def work():
    print('hello')
if __name__ == '__main__':
    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')
#结果:
#主线程/主进程
#hello

  多线程实现socket:待补充

 

 

  

  

 

  开启一个线程很简单,引入threading包,初始化一个Thread的对象,将方法名和参数作为Thread类初始化的参数传进去,再使用Thread的对象调用start方法。

  

  python中的threading模块中还提供了Lock,Rlock,Condition,Event等常用类,他们在python中是独立于Thread模块的,但是却与线程紧密相关不可分割
  python的线程中没有优先级、线程组,也不能被停止、暂停、恢复、中断,线程只能随着线程中的代码执行完毕而被销毁
  threading模块提供的类:Thread,Lock,Rlock,Condition,[Bounded]Semaphore,Event,Timber,local
  threading 模块提供的常用方法:
  threading.currentThread():返回当前的线程变量
  threading.enumerate():返回一个包含正在运行的线程的list,正在运行指线程启动后、结束前,不包括启动前和中止后的线程
  threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的效果

from threading import Thread
import threading
from multiprocessing import Process
import os
def work():
    import time
    time.sleep(3)
    print(threading.current_thread().getName()) #返回线程名

if __name__ == '__main__':
    #在主进程下开启线程
    t = Thread(target = work)
    t.start()
    print(t.is_alive()) #True,若加入t.join 后,放在其后面就会变为False,因为未加t.join()说明t.start()是最后才运行完,所以是True
    print(threading.current_thread().getName()) #返回线程名
    print(threading.current_thread()) #主线程,返回当前的线程变量
    print(threading.enumerate()) #返回一个包含正在运行的线程list,正在运行指线程启动后、结束前,不包括启动前和终止后的线程
    print(threading.active_count()) #返回正在运行的线程数量,与len(threading.enumerate())有相同的效果
    print('主线程/主进程')
True
MainThread
<_MainThread(MainThread, started 4184)>
[<_MainThread(MainThread, started 4184)>, <Thread(Thread-1, started 4156)>]
2
主线程/主进程
Thread-1

 

 

#由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
import time, threading
# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join() # 逐个执行每个线程,执行完毕后继续往下执行
print('thread %s ended.' % threading.current_thread().name)
#当一个线程sleeping的时候,cpu就去执行其他线程的内容了,所以前四个都是同样的那一行代码
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

 守护进程;daibu

 

  锁

  锁与GIL

  同步锁

  

  Lock锁

  多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有的变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享收据最大的危险在于多个线程同时改变一个变量,将内容改乱

#线程之间数据共享
#子线程修改了全局变量num后,主线程输出的num是已经被修改过的num
from threading import Thread
from time import sleep
num = 100

def run():
    print('子进程开始')
    global num 
    num += 1
    print('子进程结束')
    
if __name__ == '__main__':
    print('主线程开始')
    #创建主线程
    t = Thread(target = run)
    t.start()
    t.join()
    print(num)
    print('主线程结束')
主线程开始
子线程开始
子线程结束
101
主线程结束

  改乱实例:

import time,threading
#假定这是银行存款
balance = 0

def change_it(n):
    #先取后存,结果应改为0
    global balance
    balance = balance + n 
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

t1 = threading.Thread(target = run_thread, args=(5,))
t2 = threading.Thread(target = run_thread,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
'''当代码正常执行时:
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

结果为0

但是t1,t2是交替运行的,如果操作系统以下面的顺序执行t1,t2
t1:x1 = balance +5

t2:x2 = balance + 8
t2:balance = x2

t1:balance = x1
t1:x1 = balance -5
t1:balance = x1

t2:x2 = balance - 8
t2:balance = x2

结果为-8
'''

try...except/finally

a = 3
try : #用于异常捕捉,比如什么未定义,符号错了等
    a = b
except:
    print('error')
#结果:error

try:
    print('before error')
    a = b
    print('after error')
except:
    print('error')
#结果:before error
#error
# #先执行try语句,直到发现了错误,不再执行异常之后的代码,然后执行except

try:
    a = b
    print(a)
except SyntaxError:
    print('<<< SyntaxError')
except NameError:
    print('<<< NameError') #因为没有b,所以会匹配到NameError,从而执行except NameError
#结果:<<< NameError

'''
try:
    a = b
    print(a)
except SyntaxError:
    print('<<< SyntaxError')
except SystemError:
    print('<<< SystemError')
#结果:NameError: name 'b' is not defined,无意义
'''

#所以为了避免没有匹配到异常,又中断代码
try:
    a = b
    print(a)
except SyntaxError:
    print('<<< SyntaxError')
except SystemError:
    print('<<< SystemError')
except:
    print('I do not know, but error')
#结果:I do not know, but error

try:
    a = b
    print(a)
except SyntaxError:
    print('<<< SyntaxError')
except SystemError:
    print('<<< SystemError')
except:
    print('I do not know, but error')
else:
    print('That is good, no error')
#结果:I do not know, but error

try:
    b = 1
    a = b
    print(a)
except SyntaxError:
    print('<<< SyntaxError')
except SystemError:
    print('<<< SystemError')
except:
    print('I do not know, but error')
else:
    print('That is good, no error')
#结果:1    That is good, no error

#try ... finally,无论是否有异常,最后都要执行的代码
try:
    a = b
    print(a)
finally:
    print('I will be here')
#结果:1   I will be here

try:
    a = b
    print(a)
finally:
    print('I will be here')
#结果:1   I will be here

 

  要保证计算正确,就要加上一把锁,当某个线程执行change_it时,该进程获得了锁,因此其他线程不能同时执行change_it,只能等待,直到锁被释放后,获得该锁后才能改。由于锁只有一个,无论有多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成冲突,通过threading.Lock()实现

import time,threading
#假定这是银行存款
balance = 0

def change_it(n):
    #先取后存,结果应改为0
    global balance
    balance = balance + n 
    balance = balance - n

lock = threading.Lock()

def run_thread(n):
    for i in range(100000):
        #先要获取锁
        lock.acquire()
        try: #用try...finally来确保锁一定会被释放
            change_it(n)
        finally:
            lock.release()
            
t1 = threading.Thread(target = run_thread, args=(5,))
t2 = threading.Thread(target = run_thread,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
from threading import Thread
import os,time
def work():
    global n
    temp=n
    time.sleep(0.1)
    n=temp-1
if __name__ == '__main__':
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print(n) #结果可能为99


#加锁
from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全


#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
    global n
    print('%s is running' %current_thread().getName())
    temp=n
    time.sleep(0.5)
    n=temp-1
if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))


#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全,在变动的中间加锁,global之后
from threading import current_thread,Thread,Lock
import os,time
def task():
    #未加锁的代码并发运行
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    #加锁的代码串行运行
    lock.acquire()
    temp=n
    time.sleep(0.5)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    n=100
    lock=Lock()
    threads=[]
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))


#加锁会让并行变为串行,那么在start后面立即使用join,就不需要加锁,也是串行的效果,但是start后面立即join,所有的代码都是串行的,而加锁,只是加锁部分即修改共享数据的部分是串行的,加锁的效率更高
from threading import current_thread,Thread,Lock
import os,time
def task():
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

  

  线程的进阶

  ThreadLocal

  在多线程环境下,每个进程都有自己的数据,一个线程是用自己的局部变量比使用全局变量好,因为局部变量只有线程自己看到,不会影响其他线程,而全局变量的修改必须加锁

  但是,局部变量也有问题,就是在函数调用的时候,传递起来很麻烦,还能通过ThreadLocal实现线程数据不共享,即线程的局部变量。

import threading
num = 0
#创建一个全局的ThreadLocal对象
#每个线程有独立的存储空间
#每个线程对ThreadLocal对象都可以读写,但是互不影响
local = threading.local()
def run(x,n): x = x+n x = x-n def func(n): #每个线程都有local.x,就是线程的局部变量 local.x = num for i in range(100000): run(local.x, n) print('%s--%d'%(threading.current_thread().name,local.x)) if __name__ == '__main__': t1 = threading.Thread(target=func,args=(6,)) t2 = threading.Thread(target=func,args=(9,)) t1.start() t2.start() t1.join() t2.join() print('num=',num)
Thread-99--0
Thread-100--0
num= 0

  分布式编程:待补充

  线性队列与进程队列用法一样

  

  死锁与递归锁

  死锁:两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在相互等待的进程称为死锁进程

  解决方法:递归锁,在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock,这个RLock内部维护着一个Lock和counter变量,counter记录了acquireacquire的次数,从而使得资源可以被多次acquire,知道一个线程所有的acquire都被release,其他的线程才能获得资源。(可以将递归锁想成是一个钥匙串,能开所有锁,用完后会归还)

#死锁
import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','egon','yuan']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
哪吒 抢到了面条
哪吒 抢到了叉子
哪吒 吃面
哪吒 抢到了叉子
egon 抢到了面条
#运行不下去了
#递归解锁
import time
from threading import Thread,RLock
fork_lock = noodle_lock = RLock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','egon','yuan']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
哪吒 抢到了面条
哪吒 抢到了叉子
哪吒 吃面
哪吒 抢到了叉子
哪吒 抢到了面条
哪吒 吃面
egon 抢到了面条
egon 抢到了叉子
egon 吃面
egon 抢到了叉子
egon 抢到了面条
egon 吃面
yuan 抢到了面条
yuan 抢到了叉子
yuan 吃面
yuan 抢到了叉子
yuan 抢到了面条
yuan 吃面

  多线程threading模块的join()方法

import threading
from time import sleep,ctime
def world():
    for i in range(2):
        print('hello world '+ctime())
        sleep(2)
def ivanli():
    for i in range(2):
        print('hello ivanli '+ctime())
        sleep(5)
if __name__ == '__main__':
    t1 = threading.Thread(target=world)
    t2 = threading.Thread(target=ivanli)
    t1.start()
    t2.start()
    print('main process')
#主线程,子线程不分次序,谁先谁执行
hello world Wed Dec 18 18:21:54 2019
hello ivanli Wed Dec 18 18:21:54 2019
main process
hello world Wed Dec 18 18:21:56 2019
hello ivanli Wed Dec 18 18:21:59 2019

 

import threading
from time import sleep,ctime
def world():
    for i in range(2):
        print('hello world '+ctime())
        sleep(9)
def ivanli():
    for i in range(2):
        print('hello ivanli '+ctime())
        sleep(5)
if __name__ == '__main__':
    t1 = threading.Thread(target=world)
    t2 = threading.Thread(target=ivanli)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('main process')
#子线程不分次序,主线程最后执行
hello world Wed Dec 18 18:26:00 2019
hello ivanli Wed Dec 18 18:26:00 2019
hello ivanli Wed Dec 18 18:26:05 2019
hello world Wed Dec 18 18:26:09 2019
main process

 

import threading
from time import sleep,ctime
def world():
    for i in range(2):
        print('hello world '+ctime())
        sleep(9)
def ivanli():
    for i in range(2):
        print('hello ivanli '+ctime())
        sleep(5)
if __name__ == '__main__':
    t1 = threading.Thread(target=world)
    t2 = threading.Thread(target=ivanli)
    t1.start()
    t1.join()
    t2.start()
    t2.join()
    print('main process')
#线程一个接一个的执行,是有顺序的

 

hello world Wed Dec 18 18:29:05 2019
hello world Wed Dec 18 18:29:05 2019
hello ivanli Wed Dec 18 18:29:05 2019
hello ivanli Wed Dec 18 18:29:05 2019
main process

  

  

posted @ 2019-12-06 11:12  瞧我这个笨脑袋  阅读(264)  评论(0)    收藏  举报