从Redis中的BGSAVE命令谈起Fork—之一
来源:互联网 发布:采购软件 编辑:程序博客网 时间:2024/06/03 08:07
引言
本人近日在读黄建宏先生的《Redis设计与实现》中RDB文件的创建与载入一节,了解到SAVE命令和BGSAVE命令的实现。
SAVE:其中SAVE命令是阻塞式的,它会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
BGSAVE:和SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(即父进程)继续处理命令请求。
本文由BGSAVE命令的实现谈起Python/OS模块中的fork函数。为了更加清晰的了解fork函数的工作原理,读者可以跟我一块在linux下查看进程(ps命令),并对这个方法做一些测试,加深理解。(需要注意的是fork这个函数和操纵系统本身结合的非常紧密,windows下无法使用os.fork()函数,必须在POSIX系统(如Liunx/Unix/Mac系统)下进行测试
第一部分
首先,我们来看一下,BGSAVE命令的实现,下面以Python语言给出其实现伪代码:
def BGSAVE(): #创建子进程 pid = fork() if pid == 0: #子进程负责创建RDB文件 rdbSave() #完成之后向父进程发出信号 signal_parent() elif pid > 0: #父进程继续处理命令请求,并通过轮询等待子进程信号 handle_request_and_wait_signal() else: #处理出错情况 handle_fork_error()
读者如果不了解fork函数或者说Python中多进程的创建,读到这段代码可能有些不太明白,没关系,请看下面(简单直接)的解释。
python中的os.fork()被调用后就会立即生成一个子进程,是通过copy父进程的地址空间和资源来实现子进程的创建,同时:
- fork函数在子进程中返回的是0;
- 在父进程中返回的是子进程的PID;
- 出现错误,fork返回一个负值;
所以,我们可以通过fork的返回值来判断当前进程是子进程还是父进程。现在再返回去看这个程序是不是就很清楚了,没理解也没关系,下面进行详细的讨论。
第二部分
首先我们来写一个简单的Python脚本,这个程序运行时,系统会生成一个新的进程,看下面代码:
#!/usr/bin/env python#coding=utf8from time import sleepprint "Main thread!"sleep(3000)
因为代码执行完后,进程就会被销毁,所以这里让程序休眠30秒,方便看到效果。在linux下执行这个代码:
python hello.py &
加上&符号,可以让程序在后台运行,不会占用终端。输入ps -l命令查看进程,在终端输出如下:
其中第二条记录就是刚才运行的python程序。
接着,我们使用fork来创建子进程
使用fork创建一个新进程成功后,新进程会是原进程的子进程,原进程称为父进程。如果发生错误,则会抛出OSError异常。
#!/usr/bin/env python#coding=utf8from time import sleepimport ostry: pid = os.fork()except OSError, e: passsleep(30)
运行代码,查看进程,在终端上输出如下:
由图知,运行代码后产生了两条进程,可以看出第二条python进程就是第一条的子进程。
然后,我们再来看fork进程后的程序流程
使用fork创建子进程后,子进程会复制父进程的数据信息,而后程序就分两个进程继续运行后面的程序,这也是fork(分叉)名字的含义了。在子进程内,这个方法会返回0;在父进程内,这个方法会返回子进程的编号PID。可以使用PID来区分两个进程(这个程序就类似于文章开始时候给出的BGSAVE的实现伪代码):
#!/usr/bin/env python#coding=utf8import os#创建子进程之前声明的变量source = 10try: pid = os.fork() if pid == 0: #子进程 print "this is child process." #在子进程中source自减1 source = source - 1 sleep(3) else: #父进程 print "this is parent process." print sourceexcept OSError, e: pass
上面代码中,在子进程创建前,声明了一个变量source,然后在子进程中自减1,最后打印出source的值,显然父进程打印出来的值应该为10,子进程打印出来的值应该为9。为了明显区分父进程和子进程,让子进程睡3秒,就看的比较明显了。
既然子进程是父进程创建的,那么父进程退出之后,子进程会怎么样呢?此时,子进程会被PID为1的进程接管,就是init进程了。这样子进程就不会受终端退出影响了,使用这个特性就可以创建在后台执行的程序,俗称守护进程(daemon)。
第三部分
虽然fork给予多进程编程很大的方面性,但是滥用也是会有很多大问题的,如果程序代码中有fork子进程来操作数据,但是由于fork之后,没有及时的退出,就会导致系统中的Python进程越来越多,子进程越来越多。请看Python下fork进程的测试代码:
def fork(a): def now(): import datetime return datetime.datetime.now().strftime("%S.%f") import os import time print now(), a if os.fork() == 0: print '子进程[%s]:%s' % (now(), os.getpid()) while 1: a-=10 print '子进程的a值[%s]:%s' % (now(), a) if a < 1: break print '准备退出子进程' #os._exit(0) ## 你可以在这里退出子进程 else: print '父进程[%s]:%s' % (now(), os.getpid()) while 1: a-=1 print '父进程的a值[%s]:%s' % (now(), a) if a < 0: break time.sleep(1) print '等待子进程结束...' try: result = os.wait() if result: print '子进程:', result[0], result[1] else: print '没有数据!' except: print '异常...' print '父进程...' print '最后的值:',a #exit(0) ## 你也可以在这里退出,注意,这里是父进程和子进程都共用的地方,在这里退出会导致父进程也一并退出
随后的TIPS:
os.fork() 会有两次返回值,分别是父进程和子进程的返回值
在父进程中,fork返回的值是子进程的PID;
子进程中,这个返回值为0
子进程会复制父进程的上下文
父子进程并不能确定执行顺序
os.fork()之后,子进程一定要使用exit()或者os._exit()来退出子进程环境,建议使用os._exit()。
- 从Redis中的BGSAVE命令谈起Fork—之一
- 从Redis中的BGSAVE命令谈起Fork—之二
- Redis:BGSAVE出错原因分析
- 从java中的String.intern中谈起
- 2010年终随笔之一 从年底三无谈起
- 2010年终随笔之一 从年底三无谈起
- “我”和“你”——从短信交流中的署名谈起 2014.04.09
- 教育中的反思精神——从书籍《创造:一流大学之魂》的一则评论谈起
- 递归——从汉诺塔问题谈起
- redis学习之一基本命令
- 从Visual FoxPro中的记录号与逻辑删除谈起...
- redis replica时触发了BGSAVE FAIL的问题
- Redis被bgsave和bgrewriteaof阻塞的解决方法
- Redis被bgsave和bgrewriteaof阻塞的解决方法
- redis replica时触发了BGSAVE FAIL的问题
- 李开复:从诚信谈起
- 从程序员创业谈起
- 从西游记谈起
- switch case语句
- Access-Control-Allow-Origin跨域问题
- makefile 系列知识
- 驱动程序的模块化编程
- 若允许Tomcat所有域访问,将clientaccesspolicy.xml和crossdomain.xml加入%TOMCAT_HOME%\webapps\ROOT 目录下即可
- 从Redis中的BGSAVE命令谈起Fork—之一
- lo、eth0、br0、wlan0
- makefile系列知识 --
- TCP超时重传、滑动窗口、拥塞控制、快重传和快恢复
- 最简单spring task 定时任务实现
- 3 字节的 UTF-8 序列的字节 3 无效
- Leetcode学习(26)—— Add Binary
- 4月英语总结
- Android日志Log使用