进程间的数据共享、进程池的回调函数和线程初识、守护线程

复制代码
1、进程的数据共享
进程间数据是独立的,能够借助于队列或管道实现通讯,两者都是基于消息传递的
虽然进程间数据独立,但能够经过Manager实现数据共享。

把全部实现了数据共享的比较便捷的类都从新又封装了一遍,而且在原有的multiprocessing基础上增长了新的机制 list dict等

数据共享的机制
    支持数据类型很是有限
    list dict都不是数据安全的,你须要本身加锁来保证数据安全

Manager用法:
Manager().dict()  # 建立共享的字典
Manager().list()  # 建立共享的列表
    
用代码看看:
简单解释一下with:
with能够自动关闭文件、线程锁的自动获取和释放等过后清理工做。
紧跟with后面的语句会执行对象的 __enter__() 方法,这个方法的返回值将被赋值给as后面的变量。
当with后面的代码块所有被执行完以后,将调用对象的 __exit__()方法。

from multiprocessing import Manager,Process,Lock
def work(dic,lock):
    with lock:   # 多个进程对数据进行修改,不加锁的话会致使数据不安全
        dic['count'] -= 1   # 每一个进程都对dic进行修改

if __name__ == '__main__':
    lock = Lock()
    with Manager() as m:
        dic = m.dict({'count':100})  # 建立共享的字典
        p_lst = []
        for i in range(100):
            p = Process(target=work,args=(dic,lock))
            p_lst.append(p)
            p.start()
        for p in p_lst:
            p.join()
        print(dic)





2、进程池的回调函数(同步提交apply没有回调函数)
场景:
子进程有大量的计算要去作,回调函数对结果作简单处理。
咱们能够把耗时间(阻塞)的任务放到进程池中,而后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果。

经过例子了解一下:
import os
from multiprocessing import Pool

def func(i):
    print('子进程:',os.getpid())
    return i

def call_back(res):
    print('回调函数:',os.getpid())
    print('res--->',res)

if __name__ == '__main__':
    p = Pool()
    print('主进程:',os.getpid())
    p.apply_async(func,args=(1,),callback=call_back)  # callback关键字传参,参数是回调函数
    p.close()
    p.join()

结果:
主进程: 4732
子进程: 10552
回调函数: 4732
res---> 1

从结果能够看出:
    子进程func执行完毕以后才去执行callback回调函数
    子进程func的返回值会做为回调函数的参数
    回调函数是在主进程中执行的


应用实例:

url_lst = [
    'http://www.baidu.com',
    'http://www.4399.com',
    'http://www.163.com',
    'http://www.hao123.com',
    'http://www.sina.com'
]

import re
from urllib.request import urlopen
from multiprocessing import Pool

def get_url(url):
    res = urlopen(url) # 打开连接
    web_name = re.search('www\.(.*)\.com',url) # 网站名
    print('%s finish' %web_name.group(1))
    return web_name.group(1),res.read()  # 返回网站名和链接的内容

def call_back(content):  # 把连接的内容写入文件
    web_name,con = content
    with open(web_name+'.html','wb') as f:
        f.write(con)

if __name__ == '__main__':
    p = Pool()
    for url in url_lst:
        p.apply_async(get_url,args=(url,),callback=call_back)
    p.close()
    p.join()




3、线程的理论知识
1、进程概念
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,咱们容许多个程序同时加载到内存中,在操做系统的调度下,能够实现并发地执行。
这样的设计,大大提升了CPU的利用率。进程的出现让每一个用户感受到本身独享CPU,所以,进程就是为了在CPU上实现多道编程而提出的

2、进程的缺点
进程只能在一个时间作一件事,不能同时作两件事或多件事
进程在执行的过程当中若是阻塞,例如等待输入,整个进程就会挂起,即便进程中有些工做不依赖于输入的数据,也将没法执行

3、线程的概念
60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了不少弊端,一是因为进程是资源拥有者,建立、撤消与切换存在较大的时空开销,
所以须要引入轻型进程;二是因为对称多处理机(SMP)出现,能够知足多个运行单位,而多个进程并行开销过大。
    所以在80年代,出现了能独立运行的基本单位——线程(Threads)。
        注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
                     每个进程中至少有一个线程。

4、进程和线程的关系
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通讯:进程间通讯须要IPC(队列,管道等),同一个进程中的全部线程的资源是共享的
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程操做系统中,进程不是一个可执行的实体



5、线程使用场景
当某个进程确定须要作不止一件事情的时候,好比你用QQ,你能够同时跟不少人聊天,而QQ只是一个进程,并非说你跟一我的聊天就开一个进程这样,
由于聊天这些任务操做的都是同一块数据,于是不能用多进程。应该是你开了QQ这个进程,跟别人聊天的时候在这个进程里开启多个线程跟别人聊天。



6、线程的理解
多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。
同一台计算机上多个进程,则共享这台计算机的物理内存、磁盘、打印机等其余物理资源。多线程的运行跟多进程的运行相似,是cpu在多个线程之间的快速切换。
不一样的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,好比QQ和迅雷抢资源。而同一个进程是由一个程序员的程序建立,因此同一进程内的线程是合做关系,一个线程能够访问另一个线程的内存地址,你们都是共享的。
相似于进程,每一个线程也有本身的堆栈,不一样于进程,线程库没法利用时钟中断强制线程让出CPU,能够调用thread_yield运行线程自动放弃cpu,让另一个线程运行。


7、用户级线程和内核级线程
用户级线程
内核的切换由用户态程序本身控制内核切换,不须要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu。
在用户空间模拟操做系统对进程的调度,来调用一个进程中的线程,每一个进程中都会有一个运行时系统,用来调度线程。此时当该进程获取cpu时,进程内再调度出一个线程去执行,同一时刻只有一个线程执行。


内核级线程
切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;能够很好的利用多核cpu,windows线程就是这样的。


混合实现
用户级与内核级的多路复用,内核同一调度内核线程,每一个内核线程对应n个用户线程



8、python中的线程
全局解释器锁GIL
一个进程中的多个线程可以并行么?
    在java  c++  c# 等语言中是能够的
    可是在python中是不能够的
    python是一个解释型语言,全部的解释型语言都不行

为何不行?
Cpython解释器内部有一把全局解释器锁 GIL
同一时刻用一个进程中的线程只有一个能被CPU执行
因此线程不能充分的利用多核

GIL锁是为了保证数据的安全性,虽然确实是限制了你的程序效率
但实际上GIL锁是目前可以帮助你在线程的切换中提升效率的手段



9、总结
进程是
    计算机中最小的资源分配单位
    进程对于操做系统来讲仍是有必定负担
    建立一个进程 操做系统要分配的资源大体有 :
        代码
        数据
        文件

为何要有线程
    轻量级的概念
    他没有属于本身的进程资源:
        一条线程只负责执行代码,没有本身独立的代码、变量、文件资源
        
什么是线程
    线程是计算机中被CPU调度的最小单位
    你的计算机当中的cpu都是执行的线程中的代码
    
线程和进程之间的关系
    每个进程中都有至少一条线程在工做
    
线程的特色
    同一个进程中的线程共享这个线程的全部资源
    轻量级 没有本身的资源
    
进程和线程之间的区别
    占用的资源
    调度的效率
    资源是否共享
    
通用的问题
    一个进程中的多个线程可以并行么?
    在java c++ c# 等语言中是能够的

python中的线程
    在python中一个进程中的多个线程可以并行么? 不行
    python是一个解释型语言,全部的解释型语言都不行
        为何不行?
        Cpython解释器 内部有一把全局解释器锁 GIL
            因此线程不能充分的利用多核
            同一时刻用一个进程中的线程只有一个能被CPU执行
        GIL锁是为了保证数据的安全性,虽然确实是限制了你的程序效率
        但实际上GIL锁是目前可以帮助你在线程的切换中提升效率的手段
        GIL并非Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。

线程有:
IO密集型线程和计算密集型线程
cpython解释器适合IO密集型线程(web 爬虫 金融分析)

若是要写计算密集型的线程:
要么换解释器,要么用多进程






4、threading模块
multiprocess模块彻底模仿了threading模块的接口,两者在使用层面,有很大的类似性
1、线程的建立方式
方式一:
from threading import Thread
import time

def sleep_boy(name):
    time.sleep(1)
    print('%s is sleeping' %name)

t = Thread(target=sleep_boy,args=('xiaoming',))  # 这里能够不须要main,由于如今只是在一个进程内操做,不须要导入进程就不会import主进程了
t.start()
print('主线程')



方式二:
from threading import Thread
import time

class Sleep_boy(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        time.sleep(1)
        print('%s is sleeping' % self.name)

t = Sleep_boy('xiaoming')
t.start()
print('主线程')


2、并发性(注意:在主进程下开启多个线程,每一个线程的pid都跟主进程的pid同样)
import os
import time
from threading import Thread

def func(i):
    time.sleep(0.5)
    print('子线程:',i,os.getpid())

print('主进程:',os.getpid())
for i in range(10):
    t = Thread(target=func,args=(i,))
    t.start()


3、线程共享进程的资源
from threading import Thread

num = 100  # 全局变量

def func():
    global num
    num -= 1

t_lst = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)
for t in t_lst:
    t.join()
print('num:',num)


4、其余方法
Thread实例对象的方法
    isAlive(): 返回线程是否活动的。
    is_alive(): 返回线程是否活动的。
    getName(): 返回线程名。
    setName(): 设置线程名。



from threading import Thread
import time
def func():
    time.sleep(0.2)
    print('hello')
t = Thread(target=func)
t.start()
print(t.isAlive())  # True
print(t.is_alive()) # True
print(t.getName())  # Thread-1
t.setName('t1')
print(t.getName())  # t1



threading模块提供的一些方法:
  threading.currentThread(): 返回当前线程的对象(经过这个对象能够查看线程的一些属性,好比线程id:ident,线程的名字:getName等)
  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。



from threading import currentThread,enumerate,activeCount,Thread
import time
def func():
    print('子线程:',currentThread().ident)     # 子线程: 6076
    print('子线程:',currentThread().getName()) # 子线程: Thread-1
    time.sleep(3)

print('主线程:',currentThread().ident)      # 主线程: 1156
print('主线程:',currentThread().getName())  # 主线程: MainThread
t = Thread(target=func)
t.start()
print(enumerate())  # [<_MainThread(MainThread, started 1156)>, <Thread(Thread-1, started 6076)>]
print(len(enumerate())) # 2
print(activeCount())    # 2 5、守护线程
守护进程和守护线程的区别:
    1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),而后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(不然会产生僵尸进程),才会结束
    2 主线程在其余非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。由于主线程的结束意味着进程的结束,因此主线程结束了以后,守护线程随着主进程的结束天然结束了
  
 
import time
from threading import Thread
def func1():
    while True:
        time.sleep(0.5)
        print('func1')

def func2():
    print('func2 start')
    time.sleep(3)
    print('func2 end')

t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.setDaemon(True)  # 设置守护线程
# t1.daemon = True t1 = Thread(target=func1,daemon = True) 这两种方式也是开启守护线程的方法 t1.start() t2.start()
print('主线程代码结束了')

守护进程:只能用 p.daemon = True 这种方式设置守护进程,由于daemon只是Process类对象的属性,
而守护线程,可使用 t1 = Thread(target=func1,daemon = True)和t1.daemon = True这两种方式和t1.setDaemon(True)这种方法设置守护线程
由于daemon是Thread类对象的属性和对象的默认参数


守护进程(一种方式)
p = Process(target=func)
p.daemon = True # daemon是Process的属性

守护线程(三种方式)
t1 = Thread(target=func1,daemon = True)
t1.daemon = True
t1.setDaemon(True)



进程:



线程:

 
  
复制代码
相关文章
相关标签/搜索