进程与线程

  一、进程  

  1.1、进程概念

  每一个正在运行的程序就是一个进程,进程是一个资源的集合,需要注意的是同一个程序每次运行都是一个不同的进程。

 

  1.2、串行、并行、并发的概念

  需要首先说明的是串行、并行、并发都是说的CPU处理程序的方式,并且一个CPU同一时刻只能运行一个程序。串行指的是单个CPU在当前程序结束前无法执行其他程序,并行指的是多个CPU同时处理程序使得同一时刻多个程序同时被执行,并发指的是单个CPU在当前程序执行I/O或者占用CPU过长时切换执行其他程序使得看起来多个程序被同时执行。

 

  1.3、并发的关键技术

  CPU在并发执行程序时如果仅仅只是在不同程序间切换是无法实现并发的效果的,CPU在切换到其他程序前需要将当前程序的执行状态保存起来以便于在执行该程序时能够继续切换前的状态继续执行。

 

  1.4、进程创建的方式

  1.4.1、系统初始化

  1.4.2、父进程创建子进程

  1.4.3、用户执行应用程序

  1.4.4、批处理作业的初始化

 

  1.5、同步与异步

   同步:多个相互关联的进程中一个进程需要等待另外一个进程执行完毕才能执行

   异步:多个相互关联的进程各自执行,互相都不需要等待其他进程

 

  1.6、Linux与Windows创建进程的区别

   Linux:所有进程由init进程创建,父进程创建子进程时子进程复制父进程当前的内存空间

   Windows:没有类似Linux中init这样的进程,父进程创建子进程时,子进程是一个全新的进程,与父进程没有相同的地方

 

  1.7、Cpython中的进程

   1.7.1、multiprocessing模块

    我们可以使用multiprocessing模块的Process类创建进程,使用方法如下

    

from multiprocessing import Process
import time
import random
def sleep(name):
    print('%s is sleeping'%name)
    time.sleep(random.randint(1,3))
    print('%s is w'%name)
if __name__=="__main__":
    p1=Process(target=sleep,args=('alex',)) #target参数接收函数名,表示进程需要执行的任务;args参数接收前面函数需要的参数,以元祖的方式传递,必须由逗号;kwargs参数作用args相同,以字典形式传递
    p2=Process(target=sleep,args=('egon',))
    p3=Process(target=sleep,args=('eva',))
    p1.start() #启动进程
    p2.start()
    p3.start()
    print('主程序')

 

  1.7.2、守护进程

   守护进程就是会随着主进程代码执行完毕而被结束的子进程,守护进程必须在进程启动前设置并且守护进程无法开启子进程。设置方式如下

    

def t():
    print('%s is runing'%os.getpid())
    time.sleep(10)

if __name__ == '__main__':
    p1=Process(target=t)
    p2=Process(target=t)
    p1.daemon=True #将该进程设置为守护进程
    p2.daemon=True
    p1.start()
    p2.start()
    time.sleep(3)
    print('zhu')

 

    1.7.3、join

    join的作用就是让主进程等待子进程执行结束后回收了子进程资源后再执行后面的程序然后结束主进程,避免出现僵尸进程,因此join也必须再进程启动后设置。设置方式如下

    

def t():
    print('%s is runing'%os.getpid())
    time.sleep(10)

if __name__ == '__main__':
    p1=Process(target=t)
    p2=Process(target=t)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('zhu')

 

     1.7.4、了解部分

     p.terminate()  强制关闭当前进程,在不确定当前进程是否开启子进程的情况下慎用。

     p.is_alive()  检测当前进程是否还在执行

     p.name  获取进程名

     p.pid  获取进程id

 

    1.7.5、互斥锁

    互斥锁的作用是保证多个进程使用共享资源时有序,尤其在操作同一个数据时不出现混乱。使用互斥锁需要从multiprocessing模块中导入Lock类,然后实例化互斥锁对象,在使用时acquire申请互斥锁,release释放互斥锁,使用方式如下

    

from multiprocessing import Process,Lock
def
search(): ticker = json.load(open('test')) print('剩余%s张票' % ticker['count']) def get(): ticker = json.load(open('test')) if ticker['count'] >0: ticker['count'] -= 1 time.sleep(random.randint(1,3)) json.dump(ticker,open('test','w')) print('%s购票成功'%os.getpid()) def task(mutex): search() mutex.acquire() #给当前进程添加互斥锁 get() mutex.release() #进程执行结束释放互斥锁,不释放互斥锁会导致其他进程永远无法访问共享资源 if __name__ == '__main__': mutex=Lock() #实例化互斥锁对象 for i in range(50): Process(target=task,args=(mutex,)).start()

 

    1.7. 6、队列

     队列是是进程间通信的一种推荐使用的方式,因为它自动添加互斥锁,免去程序员自己去处理复杂的加锁解锁的流程。multiprocessing模块的Queue类中有Queue先进先出队列,还有JoinableQueue,这个队列有两个特别的方法task_done()这个方法每次从队列中取值都会给join方法发一个信号,join会等接收到与之前放入的数据数量相等的task_done()发来的确认信息再结束等待状态。

     multiprocessing模块的Queue队列:

     

#最常用的操作
q=Queue() #实例化队列对象,可以设置最大容量,不设置则为无限制
q.put('first')  #将数据放入队列
q.get('first')  #从队列中取数据

 

    multiprocessing模块的JoinableQueue队列:

    

#生产者消费者模型中的应用
from multiprocessing import JoinableQueue,Process
import time
def consumer(q):
    while True:
        res=q.get()
        time.sleep(3)
        print('%s 吃了 %s'%(os.getpid(),res))
        q.task_done() #给join发确认信息
def producter_bun(q):
    for i in range(5):
        time.sleep(2)
        res='包子%s'%i
        q.put(res)
        print('%s 制造了 %s'%(os.getpid(),res))
    q.join() #接收到与队列中数据量相应的确认信息后结束等待状态
def producter_bone(q):
    for i in range(5):
        time.sleep(2)
        res='骨头%s'%i
        q.put(res)
        print('%s 制造了 %s'%(os.getpid(),res))
    q.join()
if __name__ == '__main__':
    q = JoinableQueue()
    p1=Process(target=producter_bun,args=((q,)))
    p2=Process(target=producter_bone,args=((q,)))
    p3=Process(target=consumer,args=((q,)))
    p4=Process(target=consumer,args=((q,)))
    p3.daemon=True #当生成者进程结束时队列中数据已被取完,消费者进程没有存在的必要,跟随主进程结束即可,所以设置为守护进程
    p4.daemon=True
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    p1.join() #等待生成者进程结束
    p2.join()
    print('')

 

     1.7.7、管道

     

from multiprocessing import Process, Pipe

def f(conn):

    conn.send([42, None, 'hello']) #child_conn发送数据到管道

    print("from parent:",conn.recv()) #接收parent__conn发送到管道的数据

    conn.close()

if __name__ == '__main__':

    parent_conn, child_conn = Pipe() #一个管道有两头

    p = Process(target=f, args=(child_conn,))

    p.start()

    print(parent_conn.recv())  # 接收child_conn发送到管道的数据,prints "[42, None, 'hello']"

    parent_conn.send("ok") #发送数据到管道中

 

    1.7.8、共享数据

    共享数据也是进程间通信的一种方式,使用multiprocessing模块中的Manger类生成一个可以被共享的数据,多个进程通过访问这个数据完成通信,用法如下

    

from multiprocessing import Manger,Process,Lock
def work(dic,mutex):
     with mutex:
        dic['count'] -=1

if __name__ == '__main__':
    m=Manager()
    mutex=Lock()
    share_dic=m.dict({'count':100})
    p_l=[]
    for i in range(100):
        p=Process(target=work,args=(share_dic,mutex))
         p_l.append(p)
        p.start()
        
     for p in p_l:
         p.join()
    print(share_dic)

 

  1.7.9、进程池

  进程池可以用于多进程并发执行任务,并且可以控制最多并发处理的任务数量,使用方法有两种apply_async异步并发,apply同步串行这个实际就是apply_async之后直接用get获取数据。用法如下

  apply:

  

from multiprocessing import  Pool
import os
import time
def task(i):
    print('%s is running'%os.getpid())
    time.sleep(2)
    print('%s is done'%os.getpid())
    return i**2

if __name__ == '__main__':
    p=Pool()
    obj_l=[]
    for i in range(1,7):
        obj=p.apply(task,args=(i,))
        obj_l.append(obj)
    for obj in obj_l:
        print(obj)
----------------------------------------------------------------------------------
2160 is running
2160 is done
2156 is running
2156 is done
5024 is running
5024 is done
11748 is running
11748 is done
2160 is running
2160 is done
2156 is running
2156 is done
1
4
9
16
25
36

 

  apply_async:

   

from processing import Pool
import time
import os
def task(i):
    print('%s is running'%os.getpid())
    time.sleep(2)
    print('%s is done'%os.getpid())
    return i**2

if __name__ == '__main__':
    p=Pool()
    obj_l=[]
    for i in range(1,7):
        obj=p.apply_async(task,args=(i,))
        obj_l.append(obj)
    p.close()  #不再添加新任务
    p.join()
    for obj in obj_l:
        print(obj.get())
----------------------------------------------------------------------------------
32 is running
6684 is running
232 is running
220 is running
32 is done
32 is running
6684 is done
6684 is running
232 is done
220 is done
32 is done
6684 is done
1
4
9
16
25
36

 

    1.7.10、回调函数

     回调函数就是当前函数执行完之后的结果需要其他函数进行处理时调用的那个函数就叫回调函数,回调函数的参数就是内调用前那个函数的返回值,使用回调函数的参数为callback,具体用法如下:

    

import requests
import os
from multiprocessing import Pool

def get_page(url):
    respone=requests.get(url)
    print('<%s> get [%s]'%(os.getpid(),url))
    return {'url':url,'text':respone.text}

def parser_page(res):
    print(' <%s> parser [%s]'%(os.getpid(),res['url']))
    with open('db.txt','a') as write_f:
        content='url:%s size:%s\n'%(res['url'],len(res['text']))
        write_f.write(content)

if __name__ == '__main__':
    p=Pool()
    urls = [
        'https://www.baidu.com',
        'http://www.openstack.org',
        'https://www.python.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    for url in urls:
        p.apply_async(get_page,args=(url,),callback=parser_page) #callback参数之后的就是回调函数,其参数就是get_page函数的返回值
    p.close()
    p.join()

 

  1.7.11、信号量

  信号量有点类似进程池和互斥锁的集合,用法如下:

  

from multiprocessing import Process,Semaphore
import time,random

def go_wc(sem,user):
    sem.acquire() #一个进程进来获取一把锁并计数加一
    print('%s 占到一个茅坑' %user)
    time.sleep(random.randint(0,3)) #模拟每个人拉屎速度不一样,0代表有的人蹲下就起来了
    sem.release()

if __name__ == '__main__':
    sem=Semaphore(5) #实例化信号量的对象并设定最多可以有几个进程可以操作共享数据
    p_l=[]
    for i in range(13):
        p=Process(target=go_wc,args=(sem,'user%s' %i,))
        p.start()
        p_l.append(p)

    for i in p_l:
        i.join()
    print('============》')

 

    1.7.12、事件

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
from multiprocessing import Process,Event
import time,random

def car(e,n):
    while True:
        if not e.is_set(): #Flase
            print('\033[31m红灯亮\033[0m,car%s等着' %n)
            e.wait()
            print('\033[32m车%s 看见绿灯亮了\033[0m' %n)
            time.sleep(random.randint(3,6))
            if not e.is_set():
                continue
            print('走你,car', n)
            break

def police_car(e,n):
    while True:
        if not e.is_set():
            print('\033[31m红灯亮\033[0m,car%s等着' % n)
            e.wait(1)
            print('灯的是%s,警车走了,car %s' %(e.is_set(),n))
            break

def traffic_lights(e,inverval):
    while True:
        time.sleep(inverval)
        if e.is_set():
            e.clear() #e.is_set() ---->False
        else:
            e.set()

if __name__ == '__main__':
    e=Event()
    # for i in range(10):
    #     p=Process(target=car,args=(e,i,))
    #     p.start()

    for i in range(5):
        p = Process(target=police_car, args=(e, i,))
        p.start()
    t=Process(target=traffic_lights,args=(e,10))
    t.start()

    print('============》')

 

 

   二、线程

   2.1、线程的概念

   线程是CPU处理的最小单元,它有如下特点

   1、线程是在进程内创建的,每个进程创建时就会创建一个主线程

   2、同一个进程内的多个线程共享这个进程内的资源

   3、改变主进程的状态不会影响子进程,而改变主线程的状态会影响子线程

   4、启动线程的开销比进程小,因此启动线程比进程快

 

  2.2、线程的操作方法

  操作线程需要从threading模块导入Thread类,这个类跟multiprocessing模块的Process类一毛一样,因为Process就是模仿的Thread的接口。另外开线程的时候可以传一个参数name,就是给线程起名,这个参数当然进程也有,要查看线程名可以使用t.name或者t.getName(),方法is_alive()查看线程是否存活,threading模块还有一个方法enumerate,threading.enumerate() 返回一个列表,列表的元素是包含进程名和进程id的元祖。threading模块还有两个类currentThread(显示当前线程对象),activeCount(显示存活线程的数量)

 

  2.3、主线程结束和守护线程

   因为主线程是进程创建时被创建的,它就代表着这个进程,一旦主线程结束其所在的进程就会结束,其他非守护线程也就会被结束所以主线程必须等到其他非守护线程结束才会结束,而守护线程是随着主线程结束而结束的。

 这和进程有点不一样,守护进程在父进程代码执行完就被结束了,因为父进程在此时已经结束了,但是父进程在此时不会被回收,需要等到其他子进程结束资源被回收,父进程才会被回收。

 

  2.4、Cpython中的GIL

  GIL是全局解释器锁的简称,它负责管理代码的执行权限,只有拿到GIL的线程才能执行其代码,之所以有GIL是因为Cpython中的线程是不安全的线程,线程的执行权限会在I/O会执行时间过长等情况下被剥夺导致线程任务不能执行完,这样就可能导致数据不安全,比如python的垃圾回收线程发现一个应该被回收的数据要去清理,在没有GIL的情况下很可能数据还没清除执行权限被剥夺,然后有其他线程的变量又引用了那个要被清除的数据,然后垃圾回收线程重新拿到执行权限时就继续把那个数据给清除了,这种情况数据就不安全了,加上GIL之后在垃圾回收线程没有很耗时的操作的情况下执行权限是不会被剥夺的,这样数据就会变得安全。但是在多线程处理共享数据的时候光有GIL还不够,因为GIL只管理执行权限,不管共享数据的操作权限,需要我们再加上一个互斥锁之类的控制共享数据的操作权限。

 

  2.5、python多线程与多进程的使用场景

多进程优点:可以利用多核

多进程缺点:开销大

多进程应用场景:由于CPU是负责计算的,所以可以利用多核的多进程适合应用于计算密集型的程序,比如金融数据分析

 

多线程优点:开销小

多线程缺点:无法利用多核

多线程应用场景:由于多线程无法利用多核,所以适合I/O密集型的程序,比如网络应用

 

 2.6、互斥锁

 同进程的互斥锁

 

 2.7、递归锁

  递归锁的工作原理是,当一个线程拿到递归锁之后就会使计数加一,之后在程序内继续加锁计数继续增加,释放一次锁计数减一,只有当计数重新为零时其他线程才能拿到递归锁,进程也有递归锁,示例如下

  

from threading import Thread,RLock

mutex=RLock()

def work1():
    mutex.acquire()
    print('拿到锁1')
    mutex.acquire()
    print('拿到锁2')
    mutex.release()
    mutex.release()

def work2():
    mutex.acquire()
    print('拿到锁2')
    mutex.acquire()
    print('拿到锁1')
    mutex.release()
    mutex.release()

if __name__ == '__main__':
    for i in range(20):
        t1=Thread(target=work1).start()
        t2=Thread(target=work2).start()

 

  2.8、信号量

  同进程信号量

 

  2.9、事件

  同进程事件

 

  2.10、定时器

  定时器的作用就是在指定时间之后执行任务,示例如下

  

from threading import Timer
import time

def work(n,t):
    print(n)
    print(time.time() - t)
if __name__ == '__main__':
    s=time.time()
    t=Timer(3,work,args=(123,s)).start()
--------------------------------------------------------------------------------------
123
3.0004303455352783

 

  2.11、获取线程id

 

from concurrent.futures import ThreadPoolExecutor
from threading import currentThread
import os,time
def get_pid(n):
    print(currentThread().ident) #currentThread()获取当前线程对象,后面加.ident获取id属性的值
    time.sleep(5)
    return n**n
if __name__ == '__main__':
    p=ThreadPoolExecutor()
    obj_l=[]
    for i in range(10):
        obj=p.submit(get_pid,i)
        obj_l.append(obj)
    p.shutdown()
    for ret in obj_l:
        print(ret.result())

 

 

 

  2.12、线程队列

  线程Queue作用和用法与进程Queue基本类似,只是线程Queue多了两种进程Queue没有的队列,一个后进先出队列LifoQueue,优先级队列PriorityQueue该队列放数据时需要放入一个元祖,第一个元素是代表优先级的数字,数字越小优先级越高,第二个数据就是真正的数据

 

  2.13、进程池和线程池的简单写法

  利用concurrent.furtrue下的ProcessPoolExecutor,ThreadPoolExecutor,我们可以更加方便的开启多进程或者多线程,示例如下

  多进程:

  

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,os,random

def task(num):
    print('%s is running'%os.getpid())
    time.sleep(random.randint(1,3))
    return num**2

if __name__ == '__main__':
    p=ProcessPoolExecutor() #定义进程池,最大进程数默认为CPU个数
    l=[]
    for i in range(10):
        obj=p.submit(task,i) #开启进程,可以加.add_done_callback(函数名)调用回调函数,回调函数的参数是前面函数返回的Furture对象,Furture对象需要用.result()获取其中的值
        l.append(obj)
    p.shutdown() #相当于pool.close()和pool.join()两步
    # obj=p.map(task,range(10)) #开启进程的另一种方式,返回值是一个生成器,无法调用回调函数
    print(obj)

 

  多线程:

  代码和多进程基本一样,只需要将ProcessPoolExecutor替换为ThreadPoolExecutor即可。最大开启线程数量是CPU个数的5倍。

 

  2.13、协程

  协程就是单个线程中每一个函数的执行过程,多协程就是在单个线程中执行多个任务(函数),其中一个协程遇到I/O就切换到其他协程去执行。我们可以通过gevent这个模块来实现多协程的效果,gevent模块可以开启协程并监听I/O过程,一旦一个协程遇到I/O自动切换到其他协程去执行,当然监听I/O这个功能需要从gevent模块导入一个monkey类来实现。示例如下

  

from gevent import monkey
import gevent
monkey.patch_all()  #监听I/O,需要在被监听函数前定义
import time
def eat(name):
    print('%s eat1'%name)
    time.sleep(2)
    print('%s eat2'%name)

def play(name):
    print('%s play1'%name)
    time.sleep(3)
    print('%s play2'%name)

g1=gevent.spawn(eat,'egon')  #提交一个协程任务
g2=gevent.spawn(play,'egon')
gevent.joinall([g1,g2])  #等待所有协程执行完,主线程再执行后面的代码,也可以单独如g1.join()
#当协程的任务有返回值时可以用g1.value这样来获取返回值

  

  greenlet模块

  这个模块的作用只是在多个协程中不停切换,示例如下

  

from greenlet import greenlet

def eat(name):
    print('%s eat1'%name)
    g2.switch('egon')  #切换执行协程g2,第一次需要传参数
    print('%s eat2'%name)
    g2.switch()

def play(name):
    print('%s play1'%name)
    g1.switch()
    print('%s play2'%name)

g1=greenlet(eat) #实例化协程对象
g2=greenlet(play)
g1.switch('egon')  #第一次执行协程g1需要传参数

 

posted @ 2017-08-31 15:09  魅力宁波  阅读(191)  评论(0)    收藏  举报