今天發文比較多,哈,實在是覺得知識就該及時沈澱下來,時間長了難免記憶會模糊。
OK,直接切入正題,之前http://t.vimer.cn上提過正在開發的fuload壓力測試框架,由於是想拿python做膠水語言,所以不可避免的涉及到了進程間通信的問題。
簡單來說就是,一個python寫的主進程與多個c寫的處理進程通信的問題。主進程啟動之後,會啟動多個c的處理進程,主進程會對處理進程發送數據,並控制處理進程。
這種情況在server的編寫中比較常見,為了解耦一般會將接受數據的進程與處理進程分開,在c中的實現一般是主進程先fork出子進程,然後在子進程中調用exec將自身替換為處理進程,進程id不變。這樣主進程即可拿到所有的子進程id進行統一管理。
python當然也可以通過這種方式來實現,fork+execv即可完美重現,但是這可是無所不能的python呀,是否有更好的方式呢?
有的!python2.4之後引入了subprocess模塊,通過它,我們將不再需要繁瑣的調用fork,execv等,其主要函數如下:
以下是代碼片段:
class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
#args需要是一個字符串,或者包含程序參數的列表。要執行的程序一般就是這個列表的第一項,或者是字符串本身。但是也可以用executable參數來明確指出。當executable參數不為空時,args裏的第一項仍被認為是程序的“命令名”,不同於真正的可執行文件的文件名,這個“命令名”是一個用來顯示的名稱,例如執行*nix下的 ps 命令,顯示出來的就是這個“命令名”。
#在*nix下,當shell=False(默認)時,Popen使用os.execvp()來執行子程序。args一般要是一個列表。如果args是個字符串的話,會被當做是可執行文件的路徑,這樣就不能傳入任何參數了。
詳細可參考:http://luy.li/2010/04/14/python_subprocess/
我們來直接看一下我編寫的示例代碼,主程序(test_signal_send.py):
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
import subprocess from subprocess import Popen import signal childs = [] def handler(signo, frame): global childs for child in childs: try: child.send_signal(signal.SIGINT) except: pass def main(): global childs for i in range(0,10): p = Popen(["./test_signal_recv"]) childs.append(p) signal.signal(signal.SIGINT, handler) for child in childs: child.send_signal(signal.SIGUSR1) for child in childs: child.wait() if __name__ == "__main__": main()
然後是處理進程(test_signal_recv.cpp)(沒有在ouch中直接printf的原因是由於printf在信號處理函數中調用不安全,詳細可以參考unix網絡編程):
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
#include <error.h> #include <errno.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <poll.h> #include <sys/epoll.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <strings.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <time.h> #include <signal.h> int getsig = 0; void ouch(int sig) { getsig = 1; } int main(int argc, const char *argv[]) { signal(SIGUSR1,ouch); while(1) { printf("hello world\n"); if (getsig == 1) { printf("get signal\n"); getsig = 0; } sleep(1); } return 0; }
通過
python test_signal_send.py
運行,在另一個窗口中輸入:
ps x
終端結果如下:
PID TTY STAT TIME command 2563 ? S 0:00 sshd: dantezhu@pts/0 2564 pts/0 Ss 0:00 -bash 2778 ? S 0:00 sshd: dantezhu@pts/1 2779 pts/1 Ss 0:00 -bash 3172 pts/0 S+ 0:00 python test_signal_send.py 3173 pts/0 S+ 0:00 ./test_signal_recv 3174 pts/0 S+ 0:00 ./test_signal_recv 3175 pts/0 S+ 0:00 ./test_signal_recv 3176 pts/0 S+ 0:00 ./test_signal_recv 3177 pts/0 S+ 0:00 ./test_signal_recv 3178 pts/0 S+ 0:00 ./test_signal_recv 3179 pts/0 S+ 0:00 ./test_signal_recv 3180 pts/0 S+ 0:00 ./test_signal_recv 3181 pts/0 S+ 0:00 ./test_signal_recv 3182 pts/0 S+ 0:00 ./test_signal_recv 3185 pts/1 R+ 0:00 ps x
在主進程的窗口上輸入CTRL+C,再查看進程情況:
PID TTY STAT TIME COMMAND 2563 ? S 0:00 sshd: dantezhu@pts/0 2564 pts/0 Ss+ 0:00 -bash 2778 ? S 0:00 sshd: dantezhu@pts/1 2779 pts/1 Ss 0:00 -bash 3187 pts/1 R+ 0:00 ps x
OK,這樣主進程啟動處理進程的問題就解決了,怎麽樣,簡單吧!
接下來是數據通信的問題。
c處理進程間通信的常用方式相信大家都知道:共享內存,消息隊列,信號量,管道,信號,socket,文件mmap等。而python中只支持上述列表中的管道,信號,socket,文件mmap。
具體的篩選過程就不說了,只說最終選擇的方案是信號+文件mmap的方式。主進程發送給處理進程信號通知數據可讀,處理進程從文件mmap中讀取數據。
其實前面的例子中已經使用了信號,所以我們主要說一下mmap就行。python中是提供了mmap模塊的,我們直接調用即可。
寫文件mmap(python)(test_mmap_write.py):
1 2 3 4 5 6 7 8
import mmap wtext = 'www.vimer.cn' f = file('hello.txt','w+b') f.truncate(len(wtext)) map = mmap.mmap(f.fileno(), len(wtext)) map.write(wtext) map.flush()
讀文件mmap(c)(test_mmap_read.cpp):
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
#include <iostream> #include <string> #include <vector> #include <set> #include <map> #include <error.h> #include <errno.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <poll.h> #include <sys/epoll.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <strings.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <time.h> #include <sys/mman.h> using namespace std; unsigned long get_file_size(const char *filename) { struct stat buf; if(stat(filename, &buf)<0) { return 0; } return (unsigned long)buf.st_size; } int map_read() { char file_name[] = {"hello.txt"}; int length = get_file_size(file_name); int fd = open(file_name, O_RDWR | O_CREAT, 0644); if(fd < 0) return -1; char *buf = (char *) mmap(0, length, PROT_READ, MAP_SHARED, fd, 0); if(buf == NULL) { close(fd); return -1; } close(fd); printf("%s\n",buf); } int main(int argc, const char *argv[]) { map_read(); return 0; }
先運行:
python test_mmap_write.py
然後運行./test_mmap_read,輸出如下:
www.vimer.cn
OK,完美解決~~這些代碼都放到了fuload工程中,大家可以到https://fuload.Googlecode.com/svn/trunk/src/slave/test/查看源碼。
Tags: 進程 處理 None python executable 參數
文章來源: