线程进程协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
进程: 进程是资源分配和调度单位 
线程: 线程是执行的单位
进程池: 当我们的任务非常多时,按照以前的方法,需要创建很多的进程,进程创建过多会发生很多问题,系统会因为频繁切换进程,导致性能下降,进程池能够限制进程生成数量
线程的三种基本状态:阻塞,执行,就绪
进程的五种基本状态:派生,阻塞,激活,调度,结束
多线程的存在并不是为了提高运行速度,而是为了提高程序的使用率.(同一个进程如果执行路径多,更容易抢到CPU的执行权)
僵尸进程:当进程退出父进程没有读取到子进程退出的返回代码就会产生僵尸进程==>父进程的问题,杀死了,僵尸会变孤儿
孤儿进程:一个父进程退出,而他的一个或多个子进程还在运行,这些子进程称为孤儿进程(会被init进程收养)
僵尸进程与孤儿进程的区别:
孤儿进程是子进程还在运行,而父进程挂了,子进程被init进程收养,僵尸进程是父进程还在运行但是子进程挂了,但是父进程没有清理子进程的进程信息,导致资源浪费,而孤儿不会

进程间通信:
queue
半双工\全双工(pipe)
共享内存
manager().dict()等

多进程(multiprocessing)\多线程(threading)\协程(asyncio)
多线程是交替执行的,一次只有一个线程执行.当某个线程进入等待(io操作),则执行其他线程(相当于一个进程,用到cpu就执行这个任务,当这个任务用不到cpu就切换其他任务)
1
2
3
4
5
6
7
8
并发和并行
并行:
并行指同一时刻发生的两个或多个事件
并行是在不同实体上的多个事件
并发:
并发性是指同一时间间隔内发生两个或多个事件
并发是在同一实体上的多个事件
因此并行针对进程,并发针对线程
1
2
3
4
5
6
7
8
9
10
11
12
13
线程和进程的的关系:
1.进程内部包含所有的程序需要执行的资源
2.线程几乎不包括任何资源信息
3.线程是cpu执行的最小单位
4.进程中最少有一个线程(进程是包含进程的,可以有多个线程)
推论:
1.进程和进程的资源是不共享的
2.同一进程的线程之间资源是共享的
3.多进程的程序挂掉一个,其他进程不会受影响
4.同一个进程内的多个线程,如果进程挂了,则线程都不存在了
对编程的推论:
1.如果两个任务之间不共享任何资源(很少),最好用多进程
2.如果两个任务共享资源较多,最好用多进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
进程和线程创建步骤
1.新建进程\线程类
2.启动线程进程
3.等待进程\线程
多进程:
from multiprocessing import Process
P1 = Process(target = 函数名,args = (参数))
P1.start()
P1.join()
多线程:
from Threading import thread
P1 = Thread(target= , args(,))
P1.start()
P1.join()
进程池的代码步骤:
1.创建进程池的类
2.将任务放进进程池
3.关闭进程池等待进程结束
from multiprocessing import Pool
import time
def hello(name):
print('你好啊{}'.format(name))
time.sleep(5)
print('我很好呢!!')
if __name__ == '__main__':
#创建进程池,参数表示最大支持的进程数量
pool = Pool(3)
#任务放入进程池
name_list = ['你','我','他']
for name in name_list:
pool.apply_async(func=hello,args=(name,))
#关闭进程池并等待进程池结束
pool.close()
pool.join()
1
2
3
4
5
6
7
8
9
10
协程的用处:
1.线程尽可能多的占用cpu
2.减少cpu切换线程,提高cpu的使用效果
协程代码:
import gevent
g1 = gevent.spawn(函数名,参数)
gevent.joinall([g1,g2,g3])
猴子补丁:from gevent import monkey
monkey.patch_all()
猴子补丁的作用:将time.sleep()变成gevent.sleep()从而使效果更直观,除此之外还有很多其他效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
多进程若共享资源需要导包:   

原理:队列,需要取出以后再放入
from multiprocessing import Manager
queue = Manager.Queue()
queue只有get和put两种操作
eg: (queue,url) = queue.get(timeout=5)
queue.put((func,url))
多进程添加代理需要注意的问题:
1>共享资源问题
2>更新问题,针对response的status的处理
3>会添加代理
4>会创建获取proxy和更新proxy的类
pipe管道
conn1,conn2 = Pipe() #生成管道的两边,分别传给两个进程,一个是管道这头,一个是管道宁一端
from multiprocessing import Process,Pipe
# 导入进程,管道模块

def f(conn):
conn.send([1,'test',None])
conn.send([2,'test',None])
print(conn.recv())
conn.close()

if __name__ == "__main__":
parent_conn,child_conn = Pipe(True) #产生两个返回对象,一个是管道这一头,一个是另一头,True为全双工,False为半双工(第一个对象只能接收,第二个只能发送)
p = Process(target=f,args=(child_conn,))
p.start()
print(parent_conn.recv())
print(parent_conn.recv())
parent_conn.send('father test')
p.join()

注意事项:

1、如果是windows系统, multiprocessing.Process需在if name == ‘main‘:下使用

2、args后面的参数必须是tuple类型,在这里可以认为是为整数参数20000000添加了小括号和逗号

1
2
3
4
5
6
7
并发和并行:(通俗的解释并行指的是多个事件同时进行,并发指的是两个或多个事件在同一时间间隔发生)
(一)多线程程序在一个核CPU上的运行,就是并发
(二)多线程程序在多个核的CPU上运行,就是并行

并发在任意时刻只有一个在工作,并行是都在工作
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类型于用户级线程,这些用户级线程的调度也是自己实现的.
线程:一个线程可以跑多个协程,协程是轻量级线程

多线程和多进程的使用场景

1
2
3
4
5
6
io操作不占用cpu(从硬盘,网络,内存读取数据都算io)
计算占用cpu(如1+1计算)
Python中的线程是假线程,不同线程之间的切换是需要消耗资源的,因为需要存储线程的上线文,不断的切换就会消耗资源.
Python多线程适合io操作密集型的任务(如socket server网络并发这一类的)
python多线程不适合cpu密集操作型的任务,主要使用cpu来计算,如大量的数学计算,CPU密集型的任务可以使用多进程来操作
进程可以起多个,但是8核cpu只能对8个任务进行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import multiprocessing
import time,threading

def thread_run():
print (threading.get_ident()) #get_ident获取当前线程id

def run(name):
time.sleep(2)
print ('heelo',name)
t = threading.Thread(target=thread_run,) #在每个进程中又起了1个线程
t.start()

if __name__ == '__main__':

for i in range(10): #起了10个进程
p = multiprocessing.Process(target=run,args=('bob%s' %i,))
p.start()

默认进程之间数据是不共享的,如果一定要实现互访可以通过QUEUE实现,这个queue和线程中的queue使用方法一样,不过线程中的queue只能在线程之间使用

1
2
并发指的是多个线程被一个cpu轮流切换着执行(理解为在做一件事空闲的时候同时做宁外一件事)===>理解为交替执行
并行指的是被多个CPU执行 ====>同时执行
PiPe(两个进程间的通信,两个进程分别位于管道的两端)
1
2
3
4
5
6
7
8
9
10
11
12
13
多进程中的管道是用来实现进程间的通信的,两个进程通信,需要在内存中开辟一个空间.
单向通信:半双工 a进程写,b进程读
双向通信:全双工 a进程写,b进程读,也可以b进程写,a进程读
Pipe(duplex)
功能:创建一个管道
参数:duplex默认值为True,表示管道为双向管道(全双工)如果设置为False则为单项管道(半双工)
返回值:返回两个管道流对象,两个管道流对象分别表示管道的两端,如果参数为True的时候,两个对象均可发送接收,如果为False时,则第一个对象只能接收,第二个就只能发送
总结:
1.向管道发送数据用send()函数,从管道接收数据使用recv()函数
2.recv()函数为阻塞函数,当管道中数据为空的时候会阻塞
3.一次recv()只能接收一次send()的内容
4.send可以发送的数据类型比较多样,字符串,数字,列表等等
注意一端发送了,宁一端必须接收

获取多进程的结果然后处理

1
2
3
4
5
6
ls = []
for i in range(3):
res = pool.apply_async(func,args=(i,))
ls.append(res)
for data in ls:
print(data.get())

进程间通信之共享内存(python3.8)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from multiprocessing import Value,Array

obj = Value(ctype,data) #开辟共享内存 ctype 表示共享内存空间类型,'i','f','c'等
#data 共享空间初始数据 返回共享空间对象
obj.value #对该属性的修改查看即对共享内存读写

obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype:共享数据类型
data:整数则表示开辟空间建的大小,其他数据类型则表示开辟空间存放的数据
Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。


'''
Value实例
'''

from multiprocessing import Value,Process
import time
import random


# 创建共享内存

money = Value('i',5000)


#操作共享内存

def man():
for i in range(30):
time.sleep(0.2)
money.value += random.randint(1,1000)

def girl():
for i in range(30):
time.sleep(0.15)
money.value -= random.randint(100,800)

m = Process(target=man)
g = Process(target=girl)

g.start()
m.start()

m.join()
g.join()

print("一月余额:",money.value)


'''
Array实例
'''

from multiprocessing import Process,Array

shm = Array('c','dd')

def fun():
for i in shm:
print(i)


p = Process(target=fun)
p.start()
p.join()

####多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def func_aaa(i):
print(i)
time.sleep(2)
return i[0]**3,i[1]*10

def run_thread_pool_sub(name_list):
with ThreadPoolExecutor(max_workers=2) as t:
res = [t.submit(func_aaa, i) for i in name_list]
for future in res:
print(8979987)
data = future.result()
print(data)
name_list = [[1,9999],[2,9999],[3,9999],[4,9999],[5,9999],[6,9999]]
run_thread_pool_sub(name_list)