第36篇 多进程的数据共享,进程池的回调函数,线程 什么是GIL锁,Threading模块记

Posted cavalier-chen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第36篇 多进程的数据共享,进程池的回调函数,线程 什么是GIL锁,Threading模块记相关的知识,希望对你有一定的参考价值。

内容概览:
进程
数据共享
进程池--回调函数

线程
线程的基础理论
什么是线程?
线程与进程的关系
GIL锁
线程的开启:
Threading模块
1,用多进程开启socket创建聊天
server端写了input函数会报错?因为服务器是高速运行的,自动化的为来访问的客户端提供服务,
不可能停下来等待管理员的输入,然后发送给客户。这就失去了自动化的意义。

2,进程池Pool()方法创建的进程,map()方法是否有返回值?
p.map()得到的是迭代对象


import time
from multiprocessing import Pool,Queue,Pipe

def func(q):
    print(q)
    time.sleep(1)
    return q

if __name__ == ‘__main__‘:
    p = Pool()
    ret = p.map(func,range(10))
    for i in ret:
        print(‘-->‘,i)

  



0
1
2
3
4
5
6
7
8
9
--> 0
--> 1
--> 2
--> 3
--> 4
--> 5
--> 6
--> 7
--> 8
--> 9

  





import time,os
from multiprocessing import Pool,Queue,Pipe

def func(q):
    print(q)
    time.sleep(1)
    print(os.getpid())#打印进程的pid
    return q

if __name__ == ‘__main__‘:
    p = Pool()
    ret = p.map(func,range(10))
    for i in ret:
        print(‘-->‘,i)

  


#CPU是4核的,进程池默认最多开4个进程
0
1
2
3#这4个几多几乎同时得到
8156#进程PID1
4
7768#进程PID2
5
1780#进程PID3
6
7180#进程PID4
7
8156
8
7768
9
1780
7180
8156
7768
--> 0#进程的返回值
--> 1
--> 2
--> 3
--> 4
--> 5
--> 6
--> 7
--> 8
--> 9

  



内容回顾:
# p = Pool()默认是CPU的个数
#使用进程池提交任务
#p.apply()
同步提交,
函数直接可返回结果
ret = p.apply(函数名,参数)
#p.apply_async 异步提交
#不会直接返回函数的结果,返回的是一个对象,可以通过get()方法取值
#ret = p.apply_async(函数名,参数)
#ret_list.append(ret)
#for i in ret_list:
print(i.get())
#p.close()关闭进程池
#p.join()
#map方法
#得到的是返回值的可迭代对象
#通过for循环就可以取值

多进程与进程池的区别:
多进程:
由操作系统统一来调度
常见的进程越多,占用的资源就越多
操作系统的效率就会下降

进程池:
进程池中运行的进程数量是非常有限的
用户量比较大的时候就会导致,服务器的响应变慢


数据共享:
from multiprocessing import Manager,Process,Lock

def work(dic,lock):
    dic[‘count‘] -= 1
if __name__ == ‘__main__‘:
    lock = Lock()
    m = Manager()
    dic = m.dict({‘count‘:100})#Manager类中的dic()方法,传入参数将字典设置成共享数据
    p_list = []
    for i in range(20):
        p = Process(target=work,args=(dic,lock))
        p_list.append(p)
        p.start()
    for p in p_list:
        p.join()
    print(dic)

{‘count‘: 80}
说明:
    开启20个进程,异步的哦,对共享的字典进行修改,本来,进程去取数据,修改之后再放回去,
    但是由于进程额执行是需要一段时间的,有可能两个进程同时取到的都是99,然后修改再放进去,
    这样就会出现偏差。

  

技术分享图片

 

 



解决问题的办法就是加锁,只能允许一个进程进来取数据,
from multiprocessing import Manager,Process,Lock

def work(dic,lock):
    lock.acquire()
    dic[‘count‘] -= 1
    lock.release()
if __name__ == ‘__main__‘:
    lock = Lock()
    m = Manager()
    dic = m.dict({‘count‘:100})#Manager类中的dic()方法,传入参数将字典设置成共享数据
    p_list = []
    for i in range(20):
        p = Process(target=work,args=(dic,lock))
        p_list.append(p)
        p.start()
    for p in p_list:
        p.join()
    print(dic)

  


{‘count‘: 80}

  



有时间的话研究一下:
with 的用法
dis模块
面向对象的魔术方法:__enter__ __exit__

cpu的时间观念

技术分享图片

 



回调函数:
回调函数的作用,以及应用场景:
子进程有大量的计算,或者访问网页,需要一定的时间才能完成,
回调函数等待子进程的结果,然后做简单的处理,得到结果
例如:例如子进程访问网页,主进程将网页写入文档

url_list = [
    ‘http://www.baidu.com‘,
    ‘http://www.sohu.com‘,
    ‘http://www.sogou.com‘,
    ‘http://www.cnblogs.com‘,
]

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

def get_url(url):
    respons = urlopen(url)
    pattern = ‘www.(.*?).com‘
    ret = re.search(pattern,url)
    print(‘%s finished‘%ret.group(1))
    return ret.group(1),respons.read()

def write(content):
    url,con = content
    with open(url+‘.html‘,‘wb‘) as f:
        f.write(con)

if __name__ == ‘__main__‘:
    p = Pool()
    for url in url_list:
        p.apply_async(get_url,args=(url,),callback=write)
    #采用异步提交的方法p.apply_async,不能忘记写上close、join
    p.close()
    p.join()

  


baidu finished
sohu finished
sogou finished
cnblogs finished

  



正式进入线程
先回顾一下进程
程序是不能单独的自己运行,只有将程序加载到内存,有操作系统分配资源,在能运行,这种执行中额程序就叫做进程
进程与程序的区别:
程序是指令的集合,是程序运行的静态描述文本,
进程是程序的一次执行活动,
多道编程中,允许多个程序同时加载到内存中,在操作系统的调度下,,可以实现并发的执行,这样大大的提高了CPU的效率,
进程的出现,让用户感觉自己是独享CPU

线程的出现
60年代,操作系统中,能够拥有资源和独立运行的基本单位是进程,进程出现一些弊端:
进程是资源的拥有者,创建,撤销和切换会消耗大量的时间,多个进程并行开销过大
于是出现了能够独立运行的基本单位---线程thread
注意两点:
进程是资源分配的基本单位
线程是调度的最小单位
每个进程至少有一个线程

技术分享图片

 



进程与线程的区别:
#资源共享:
进程之间是相互独立的,同一个进程的各个线程之间资源共享,
#通讯:
线程间是可以直接读取所在的进程中的数据段(比如:全局变量)来进行通讯,
进程之间的通讯:队列,管道。。
#切换速度:
线程的上下文切换,比进程快得多

线程的特点:
通常情况下,一个进程包含多个线程,么个线程作为利用CPU的基本单位,是花费自小开销的实体,
1)轻型实体
线程中的实体基本不拥有系统资源,只拥有:
程序,数据,TCB包,线程是动态概念,动态特性有线程控制模块TCB(thread control block)描述:
TCB包含以下信息:
线程状态
线程不运行时,线程数据
一组执行堆栈
线程的局部变量
访问同一个进程的主存 和 其他资源
2)独立调度和分派的基本单位
在多线程操作系统中,线程是能够独立运行的基本单位,因而也是能够独立调度和分派的基本单位,
由于线程的很轻,因此线程的切换非常迅速,开销很小
3)共享进程的资源
在同一进程中的各个线程,可以共享该进程所拥有的资源,具有相同的pid,因此可以访问进程的每一个内存资源,
此外还可以访问进程所拥有的已打开文件,定时器,信号量机构等。
4)可并发性
在一个进程的多个线程之间,可以并发执行,

全局解释器锁GIL
虽然python解释器中可以运行多个线程,但是在任意时刻只有一个线程在解释器在解释器中运行,
对python虚拟机的访问,由全局解释器锁GIL控制,正是这个锁保证同一时刻只能一个线程在运行,

线程之间的切换,python虚拟机按一下方式执行:
设置GIL
切换到一个线程去运行
运行指定数量的字节码指令,或者线程主动让出控制
把线程设置成睡眠状态
解锁GIL
重复以上的步骤

Thread模块:
子线程的开启
import time,os
from threading import Thread
def func(i):
    time.sleep(0.5)
    print(‘子线程:‘,i,os.getpid())

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

  


进程: 0 8740
进程: 1 8740
进程: 2 8740
进程: 3 8740
进程: 4 8740
进程: 5 8740
进程: 6 8740
进程: 7 8740
进程: 8 8740
进程: 9 8740
子线程: 0 8740
子线程: 1 8740
子线程: 2 8740
子线程: 3 8740
子线程: 4 8740
子线程: 5 8740
子线程: 6 8740
子线程: 9 8740
子线程: 8 8740
子线程: 7 8740

  



进程与线程,线程与线程之间数据共享
变量可以相互的调用
from  threading import  Thread
num = 100
def func():
    global num
    num -= 1
    print(‘线程‘,num)

t_list = []

for i in range(100):
    t = Thread(target=func)
    t.start()
    t_list.append(t)
for i in t_list:
    t.join()
print(‘进程‘,num)

  




线程的几个方法:
t.setName()
t.getName()
t.is_alive()
currentThread()
currentThread().ident

from  threading import  Thread
import time
def func():
    time.sleep(3)

t = Thread(target=func)
t.start()
print(t.is_alive())
print(t.getName())
t.setName(‘alex‘)
print(t.getName())

  


True
Thread-1
alex

  




from  threading import  Thread,currentThread
import time
def func():
    print(‘子线程:‘,currentThread().ident)
    time.sleep(3)

print(‘主线程‘,currentThread().ident)
t = Thread(target=func)
t.start()

  



主线程 6752
子线程: 9324

  



#from threading import Thread,currentThread,enumerate,activeCount
查看当前的进程,
查看进程数,
import time
from  threading import  Thread,currentThread,enumerate,activeCount

def func():
    print(‘子线程:‘,currentThread().ident)
    time.sleep(3)

print(‘主线程‘,currentThread().ident)
for i in range(10):
    t = Thread(target=func)
    t.start()
print(len(enumerate()))
print(activeCount())

  



守护线程:
t1.setDaemon(True)

import  time
from threading import Thread
def func1():
    while True:
        time.sleep(0.5)
        print(‘子线程1‘)

def func2():
    print(‘func2 start‘)
    time.sleep(2)
    print(‘func2 end‘)
t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.setDaemon(True)#设置成守护线程
t1.start()
t2.start()
print(‘主线程结束‘)

  



func2 start
主线程结束
子线程1
子线程1
子线程1
func2 end

守护线程:
主代码结束之后,会等待子线程执行结束之后 才会结束

使用threading模块 写一个TCP通讯

sever端:

import socket
from threading import Thread
def communicate(conn):
    while True:
        conn.send(b‘hello‘)
        print(conn.recv(1024))
if __name__ == ‘__main__‘:
    sk = socket.socket()
    sk.bind((‘127.0.0.1‘,8989))
    sk.listen()

    while True:
        conn,addr = sk.accept()
        t = Thread(target=communicate,args=(conn,))#采用线程开启
        t.start()

  


client端:
import  socket
sk = socket.socket()
sk.connect((‘127.0.0.1‘,8989))
while True:
    print(sk.recv(1024))
    sk.send(b‘bye‘)

  






























































































































































































以上是关于第36篇 多进程的数据共享,进程池的回调函数,线程 什么是GIL锁,Threading模块记的主要内容,如果未能解决你的问题,请参考以下文章

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

python全栈开发基础第二十二篇进程池和回调函数

池和回调函数

python-- 多进程的基本语法 进程间数据交互与共享进程锁和进程池的使用

Python网编_进程池的回调函数

线程池或进程池的回调函数