网络编程中的锁与队列
进程的其他方法:
1 import os,time
2 from multiprocessing import Process
3 def f1():
4 print('子进程的pid',os.getpid()) #查看当前子进程的id
5 print('子进程的父进程的pid',os.getppid())
6 print(123)
7
8 def f2():
9 print('子进程的pid',)
10 print('父进程的pid',)
11 print(123)
12
13 if __name__ == '__main__':
14 p1 = Process(target=f1,name='洪七')
15 p2 = Process(target=f2)
16 p1.start()
17 p2.start()
18 print(p1.name) #查看当前子进程的name,可以进行赋值更改name
19 print('子进程的pid',p1.pid) #查看当前子进程的id
20 print('父进程的pid',os.getpid())
21 def f3():
22 time.sleep(5)
23 print('子进程2')
24 if __name__ == '__main__':
25 p = Process(target=f3,)
26 p.start()
27 print(p.is_alive()) #判断子进程是否还活着,是否还在运行
28 p.terminate() #给操作系统发送一个结束进程的信号
29 print(p.is_alive())
验证进程之间是空间隔离的:
1 from multiprocessing import Process
2
3 num = 100
4
5 def f1():
6 global num
7 num = 3
8 print('子进程中的num',num)
9
10 print('>>>>>',num)
11 if __name__ == '__main__':
12 p = Process(target=f1,)
13 p.start()
14 p.join()
15 print('主进程中的num',num)
僵尸进程与孤儿进程(简单了解 一下)
1 一:僵尸进程(有害)
2 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下
3
4 我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。
5
6 因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
7 1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
8 2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
9
10 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
11
12 二:孤儿进程(无害)
13
14 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
15
16 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
17
18 我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容
19
20 import os
21 import sys
22 import time
23
24 pid = os.getpid()
25 ppid = os.getppid()
26 print 'im father', 'pid', pid, 'ppid', ppid
27 pid = os.fork()
28 #执行pid=os.fork()则会生成一个子进程
29 #返回值pid有两种值:
30 # 如果返回的pid值为0,表示在子进程当中
31 # 如果返回的pid值>0,表示在父进程当中
32 if pid > 0:
33 print 'father died..'
34 sys.exit(0)
35
36 # 保证主线程退出完毕
37 time.sleep(1)
38 print 'im child', os.getpid(), os.getppid()
39
40 执行文件,输出结果:
41 im father pid 32515 ppid 32015
42 father died..
43 im child 32516 1
44
45 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。
46
47
48 三:僵尸进程危害场景:
49
50 例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
51
52 四:测试
53 #1、产生僵尸进程的程序test.py内容如下
54
55 #coding:utf-8
56 from multiprocessing import Process
57 import time,os
58
59 def run():
60 print('子',os.getpid())
61
62 if __name__ == '__main__':
63 p=Process(target=run)
64 p.start()
65
66 print('主',os.getpid())
67 time.sleep(1000)
68
69
70 #2、在unix或linux系统上执行
71 [root@vm172-31-0-19 ~]# python3 test.py &
72 [1] 18652
73 [root@vm172-31-0-19 ~]# 主 18652
74 子 18653
75
76 [root@vm172-31-0-19 ~]# ps aux |grep Z
77 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
78 root 18653 0.0 0.0 0 0 pts/0 Z 20:02 0:00 [python3] <defunct> #出现僵尸进程
79 root 18656 0.0 0.0 112648 952 pts/0 S+ 20:02 0:00 grep --color=auto Z
80
81 [root@vm172-31-0-19 ~]# top #执行top命令发现1zombie
82 top - 20:03:42 up 31 min, 3 users, load average: 0.01, 0.06, 0.12
83 Tasks: 93 total, 2 running, 90 sleeping, 0 stopped, 1 zombie
84 %Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
85 KiB Mem : 1016884 total, 97184 free, 70848 used, 848852 buff/cache
86 KiB Swap: 0 total, 0 free, 0 used. 782540 avail Mem
87
88 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
89 root 20 0 29788 1256 988 S 0.3 0.1 0:01.50 elfin
90
91
92 #3、
93 等待父进程正常结束后会调用wait/waitpid去回收僵尸进程
94 但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的
95 解决方法一:杀死父进程
96 解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
97 参考python2源码注释
98 class Process(object):
99 def join(self, timeout=None):
100 '''
101 Wait until child process terminates
102 '''
103 assert self._parent_pid == os.getpid(), 'can only join a child process'
104 assert self._popen is not None, 'can only join a started process'
105 res = self._popen.wait(timeout)
106 if res is not None:
107 _current_process._children.discard(self)
108
109 join方法中调用了wait,告诉系统释放僵尸进程。discard为从自己的children中剔除
守护进程:(**)
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
守护进程代码展示:
1 import time
2 from multiprocessing import Process
3
4 def f1():
5 time.sleep(3)
6 print('xxxx')
7
8 def f2():
9 time.sleep(5)
10 print('普通子进程的代码')
11 if __name__ == '__main__':
12 p = Process(target=f1,)
13 p.daemon = True #将该进程设置为守护进程,必须写在start之前,意思如果我的主进程代码运行结束了,你这个子进程不管运行到什么地方,都直接结束
14 p.start()
15
16 #开启一个普通的子进程来验证一下守护进程的结束只和主进程的代码运行结束有关系,而整个程序的结束需要主进程和普通的子进程的代码都运行结束才结束
17 p2 = Process(target=f2,)
18 p2.start()
19 #等待2号普通进程的结束,才继续执行下面主进程中的代码
20 # p2.join()
21 #守护进程会跟跟着父进程的代码运行结束,就结束
22 print('主进程结束')
进程锁(同步锁/互斥锁) *****
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理。
简单的进程锁代码展示
1 # 互斥锁/进程锁/同步锁
2 # import json
3 import time
4 from multiprocessing import Process,Lock
5
6 def show_t(i):
7
8 with open('ticket','r',encoding='utf-8') as f:
9 ticket_data = f.read()
10 # print(ticket_data)
11 t_data = eval(ticket_data)
12 # print(t_data,type(t_data))
13 print('%s查询剩余票数为%s'%(i,t_data['count']))
14
15 def get_t(i,l1):
16 l1.acquire()
17 with open('ticket', 'r', encoding='utf-8') as f:
18 ticket_data = f.read()
19 # print(ticket_data)
20 t_data = eval(ticket_data)
21 # print(t_data,type(t_data))
22 # print('%s查询剩余票数为%s' % (i, t_data['count']))
23 if t_data['count'] > 0:
24 t_data['count'] -= 1
25 print('%s抢票成功'%i)
26 time.sleep(0.2)
27 with open('ticket', 'w') as f:
28 f.write(str(t_data))
29
30 else:
31 print('没票了!!!')
32 l1.release()
33
34 if __name__ == '__main__':
35 l1 = Lock()
36 for i in range(10):
37 p1 = Process(target=show_t,args=(i,))
38 p1.start()
39 for i in range(10):
40 p2 = Process(target=get_t,args=(i,l1) )
41 p2.start()
数据共享:
1 import time
2 from multiprocessing import Process,Manager,Lock
3
4 def f1(m_d,l2):
5 # m_d['num'] -= 1 #
6 with l2:
7 # l2.acquire()
8 tmp = m_d['num']
9 tmp -= 1
10 time.sleep(0.1)
11 m_d['num'] = tmp
12 # l2.release()
13
14 if __name__ == '__main__':
15 m = Manager()
16 l2 = Lock()
17 m_d = m.dict({'num':100})
18 p_list = []
19 for i in range(10):
20 p = Process(target=f1,args=(m_d,l2))
21 p.start()
22 p_list.append(p)
23
24 [pp.join() for pp in p_list]
25
26 print(m_d['num'])
for 循环创建多进程:
1 import time
2 from multiprocessing import Process
3
4
5 def f1():
6 time.sleep(0.5)
7 print('xxx')
8
9 if __name__ == '__main__':
10 p_list = []
11 #for循环创建子进程,并且完成主进程等待所有子进程执行结束,才继续执行
12 for i in range(10):
13 p = Process(target=f1,)
14 p.start()
15 p_list.append(p)
16 p.join()
17 # for pp in p_list:
18 # pp.join()
19
20 print('主进程结束')
队列(Queue):
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。队列就像一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出数据,先进先出。
Queue([maxsize]) 创建共享的进程队列。
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
底层队列使用管道和锁实现。
基于队列的进程通信:
1 from multiprocessing import Process,Queue
2
3 def f1(q):
4 q.put('约吗?')
5
6 if __name__ == '__main__':
7 q = Queue(3)
8
9 p = Process(target=f1,args=(q,))
10 p.start()
11
12 son_p_msg = q.get()
13
14 print('来自子进程的消息:',son_p_msg)
利用队列实现一个生产消费模型:
1 import time
2 from multiprocessing import Process,JoinableQueue
3 #生产者
4 def producer(q):
5 for i in range(10):
6 time.sleep(0.2)
7 s = '大包子%s号'%i
8 print(s+'新鲜出炉,拿去用')
9 q.put(s)
10 q.join() #就等着task_done()信号的数量,和我put进去的数量相同时,才继续执行
11 print('所有的任务都被处理了,继续潜行吧骚年们')
12
13 def consumer(q):
14 while 1:
15 time.sleep(0.5)
16 baozi = q.get()
17
18 print(baozi+'被吃了')
19 q.task_done() #给队列发送一个取出的这个任务已经处理完毕的信号
20
21 if __name__ == '__main__':
22 # q = Queue(30)
23 q = JoinableQueue(30) #同样是一个长度为30的队列
24
25 pro_p = Process(target=producer,args=(q,))
26 con_p = Process(target=consumer,args=(q,))
27 pro_p.start()
28 con_p.daemon = True
29 con_p.start()
30
31
32 pro_p.join()
33 print('主进程结束')
进程队列 ***** Queue()
Q = Queue(5)
Q.put() #满了会等待
Q.get() #没有数据了会等待
Q.qsize()
Q.empty() 不可靠
Q.full()不可靠
Q.get_nowait() #不等待,但是报错
Q.put_nowait() #不等待,但是报错