Linux多进程
来源:互联网 发布:ubuntu的apache服务器 编辑:程序博客网 时间:2024/06/06 08:16
将有关多进程的处理整理于此
1.IBM GDB
http://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/
2.在 Linux 上实现基于 Socket 的多进程实时通信
http://www.ibm.com/developerworks/cn/linux/l-socket-ipc/
3Linux多任务编程编写多进程程序及其代码
http://www.linuxidc.com/Linux/2013-06/85903p6.htm3.
//***************************************************************************************************************
实验目的
通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux中多进程编程的步骤。
实验内容
该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停5s后异常退出。父进程先用阻塞方式等待第一个进程的结束,然后用非阻塞方式等待另一个子进程的退出,待收集到第2个子进程结束的消息后,父进程就返回。
实验步骤
该实验的流程图如下
实验源代码
先看一下下面的代码,这个程序能得到我们所希望的结果吗?它的运行会产生几个进程?
执行结果如下图
也有可能会出现下面的结果
分析执行结果可以指知道,这里其实是产生了3个子进程,在子进程2中又产生了一个子进程1。
下面咱们贴出正确的实验代码:
执行结果如下图
不论程序执行多少次,结果都是一样的。对比上一个结果,咱们就可以理解多进程该怎么创建了。一定要明确创建子进程的父进程是哪一个。
相关实验代码上传到网站,multi_proc_wrong.c下载,multi_proc.c下载
免费下载地址在 http://linux.linuxidc.com/
用户名与密码都是www.linuxidc.com
具体下载目录在 /2013年资料/6月/12日/Linux多任务编程
//***************************************************************************************************************
使用 GDB 调试多进程程序
GDB 是 linux 系统上常用的调试工具,本文介绍了使用 GDB 调试多进程程序的几种方法,并对各种方法进行比较。
3评论:
2007 年 7 月 30 日
- 内容
GDB 是 linux 系统上常用的 c/c++ 调试工具,功能十分强大。对于较为复杂的系统,比如多进程系统,如何使用 GDB 调试呢?考虑下面这个三进程系统:
进程
Proc2 是 Proc1 的子进程,Proc3 又是 Proc2 的子进程。如何使用 GDB 调试 proc2 或者 proc3 呢?
实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实我们可以利用GDB的特点或者其他一些辅助手段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。
接下来我们详细介绍几种方法,分别是 follow-fork-mode 方法,attach 子进程方法和 GDB wrapper 方法。
follow-fork-mode
在2.5.60版Linux内核及以后,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。
follow-fork-mode的用法为:
set follow-fork-mode [parent|child]
- parent: fork之后继续调试父进程,子进程不受影响。
- child: fork之后调试子进程,父进程不受影响。
因此如果需要调试子进程,在启动gdb后:
(gdb) set follow-fork-mode child
并在子进程代码设置断点。
此外还有detach-on-fork参数,指示GDB在fork之后是否断开(detach)某个进程的调试,或者都交由GDB控制:
set detach-on-fork [on|off]
- on: 断开调试follow-fork-mode指定的进程。
- off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态。
注意,最好使用GDB 6.6或以上版本,如果你使用的是GDB6.4,就只有follow-fork-mode模式。
follow-fork-mode/detach-on-fork的使用还是比较简单的,但由于其系统内核/gdb版本限制,我们只能在符合要求的系统上才能使用。而且,由于follow-fork-mode的调试必然是从父进程开始的,对于fork多次,以至于出现孙进程或曾孙进程的系统,例如上图3进程系统,调试起来并不方便。
Attach子进程
众所周知,GDB有附着(attach)到正在运行的进程的功能,即attach <pid>命令。因此我们可以利用该命令attach到子进程然后进行调试。
例如我们要调试某个进程RIM_Oracle_Agent.9i,首先得到该进程的pid
[root@tivf09 tianq]# ps -ef|grep RIM_Oracle_Agent.9inobody 6722 6721 0 05:57 ? 00:00:00 RIM_Oracle_Agent.9iroot 7541 27816 0 06:10 pts/3 00:00:00 grep -i rim_oracle_agent.9i
通过pstree可以看到,这是一个三进程系统,oserv是RIM_Oracle_prog的父进程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父进程。
[root@tivf09 root]# pstree -H 6722
通过 pstree 察看进程
启动GDB,attach到该进程
用 GDB 连接进程
现在就可以调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪里了。有没有办法解决呢?
一个办法是,在要调试的子进程初始代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码可以继续执行下去。
至于这段代码所采用的条件,看你的偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。以文件为例,其形式可以如下:
void debug_wait(char *tag_file){ while(1) { if (tag_file存在) 睡眠一段时间; else break; }}
当attach到进程后,在该段代码之后设上断点,再把该文件删除就OK了。当然你也可以采用其他的条件或形式,只要这个条件可以设置/检测即可。
Attach进程方法还是很方便的,它能够应付各种各样复杂的进程系统,比如孙子/曾孙进程,比如守护进程(daemon process),唯一需要的就是加入一小段代码。
GDB wrapper
很多时候,父进程 fork 出子进程,子进程会紧接着调用 exec族函数来执行新的代码。对于这种情况,我们也可以使用gdb wrapper 方法。它的优点是不用添加额外代码。
其基本原理是以gdb调用待执行代码作为一个新的整体来被exec函数执行,使得待执行代码始终处于gdb的控制中,这样我们自然能够调试该子进程代码。
还是上面那个例子,RIM_Oracle_prog fork出子进程后将紧接着执行RIM_Oracle_Agent.9i的二进制代码文件。我们将该文件重命名为RIM_Oracle_Agent.9i.binary,并新建一个名为RIM_Oracle_Agent.9i的shell脚本文件,其内容如下:
[root@tivf09 bin]# mv RIM_Oracle_Agent.9i RIM_Oracle_Agent.9i.binary[root@tivf09 bin]# cat RIM_Oracle_Agent.9i#!/bin/shgdb RIM_Oracle_Agent.binary
当fork的子进程执行名为RIM_Oracle_Agent.9i的文件时,gdb会被首先启动,使得要调试的代码处于gdb控制之下。
新的问题来了。子进程是在gdb的控制下了,但还是不能调试:如何与gdb交互呢?我们必须以某种方式启动gdb,以便能在某个窗口/终端与gdb交互。具体来说,可以使用xterm生成这个窗口。
xterm是X window系统下的模拟终端程序。比如我们在Linux桌面环境GNOME中敲入xterm命令:
xterm
就会跳出一个终端窗口:
终端
如果你是在一台远程linux服务器上调试,那么可以使用VNC(Virtual Network Computing) viewer从本地机器连接到服务器上使用xterm。在此之前,需要在你的本地机器上安装VNC viewer,在服务器上安装并启动VNC server。大多数linux发行版都预装了vnc-server软件包,所以我们可以直接运行vncserver命令。注意,第一次运行vncserver时会提示输入密码,用作VNC viewer从客户端连接时的密码。可以在VNC server机器上使用vncpasswd命令修改密码。
[root@tivf09 root]# vncserver New 'tivf09:1 (root)' desktop is tivf09:1Starting applications specified in /root/.vnc/xstartupLog file is /root/.vnc/tivf09:1.log[root@tivf09 root]#[root@tivf09 root]# ps -ef|grep -i vncroot 19609 1 0 Jun05 ? 00:08:46 Xvnc :1 -desktop tivf09:1 (root) -httpd /usr/share/vnc/classes -auth /root/.Xauthority -geometry 1024x768 -depth 16 -rfbwait 30000 -rfbauth /root/.vnc/passwd -rfbport 5901 -pnroot 19627 1 0 Jun05 ? 00:00:00 vncconfig -iconicroot 12714 10599 0 01:23 pts/0 00:00:00 grep -i vnc[root@tivf09 root]#
Vncserver是一个Perl脚本,用来启动Xvnc(X VNC server)。X client应用,比如xterm,VNC viewer都是和它通信的。如上所示,我们可以使用的DISPLAY值为tivf09:1。现在就可以从本地机器使用VNC viewer连接过去:
VNC viewer:输入服务器
输入密码:
VNC viewer:输入密码
登录成功,界面和服务器本地桌面上一样:
VNC viewer
下面我们来修改RIM_Oracle_Agent.9i脚本,使它看起来像下面这样:
#!/bin/shexport DISPLAY=tivf09:1.0; xterm -e gdb RIM_Oracle_Agent.binary
如果你的程序在exec的时候还传入了参数,可以改成:
#!/bin/shexport DISPLAY=tivf09:1.0; xterm -e gdb --args RIM_Oracle_Agent.binary $@
最后加上执行权限
[root@tivf09 bin]# chmod 755 RIM_Oracle_Agent.9i
现在就可以调试了。运行启动子进程的程序:
[root@tivf09 root]# wrimtest -l 9i_linuxResource Type : RIMResource Label : 9i_linuxHost Name : tivf09User Name : mdstatusVendor : OracleDatabase : rimDatabase Home : /data/oracle9i/920Server ID : rimInstance Home : Instance Name : Opening Regular Session...
程序停住了。从VNC viewer中可以看到,一个新的gdb xterm窗口在服务器端打开了
gdb xterm 窗口
[root@tivf09 root]# ps -ef|grep gdbnobody 24312 24311 0 04:30 ? 00:00:00 xterm -e gdb RIM_Oracle_Agent.binarynobody 24314 24312 0 04:30 pts/2 00:00:00 gdb RIM_Oracle_Agent.binaryroot 24326 10599 0 04:30 pts/0 00:00:00 grep gdb
运行的正是要调试的程序。设置好断点,开始调试吧!
注意,下面的错误一般是权限的问题,使用 xhost 命令来修改权限:
xterm 错误
[root@tivf09 bin]# export DISPLAY=tivf09:1.0[root@tivf09 bin]# xhost +access control disabled, clients can connect from any host
xhost + 禁止了访问控制,从任何机器都可以连接过来。考虑到安全问题,你也可以使用xhost + <你的机器名>。
小结
上述三种方法各有特点和优劣,因此适应于不同的场合和环境:
- follow-fork-mode方法:方便易用,对系统内核和GDB版本有限制,适合于较为简单的多进程系统
- attach子进程方法:灵活强大,但需要添加额外代码,适合于各种复杂情况,特别是守护进程
- GDB wrapper方法:专用于fork+exec模式,不用添加额外代码,但需要X环境支持(xterm/VNC)。
在 Linux 上实现基于 Socket 的多进程实时通信
套接口(Socket)为目前 Linux 上最为广泛使用的一种的进程间通信机制。但是它不能直接用来多进程之间的相互实时通信。本文提出一个基于 Socket 的多进程之间通信的实现方法,并给出样本程序的实现和说明。
1评论:
2005 年 3 月 01 日
- 内容
套接口(Socket)为目前Linux上最为广泛使用的一种的进程间通信机制,与其他的Linux通信机制不同之处在于除了它可用于单机内的进程间通信以外,还可用于不同机器之间的进程间通信。但是由于Socket本身不支持同时等待和超时处理,所以它不能直接用来多进程之间的相互实时通信。
本文提出一个基于Socket的多进程之间通信的实现方法。原理是建立一个进程专门用来做为通信服务器(server)来中转各个进程之间的通信。它首先启动一个用来监视连接要求的listening Socket,并把它的描述(Descriptor)号加入到一个事先定义好的fd_set的集合中,这个fd_set的集合用来存放listening Socket和后来生成的通信Socket的描述号。Server运用system call select来实时检查是否有数据到达这个集合中的任何一个socket,如果有数据到达listening Socket,则这一定是客户端发起的连接请求,于是生成一个新的通信Socket与该客户端连接,将生成的Socket描述号加入到fd_set的集合中,将客户端的ID号和与之对应的Socket的描述号记录在ID登记表中。如果有数据到达某个通信Socket,则这一定是某个客户端发起的通信请求,读出数据并取出收信客户端ID号,在ID登记表中找到与之对应的Socket描述号,将数据通过对应Socket传送到收信客户端。
其他各进程作为客户端(client)。客户端的动作是首先建立通信Socket连接服务器端,然后通过通信Socket进行送信和收信。
下面给出具体的程序实现和说明,
首先给出Server端的程序,在这里假设有两个客户端要进行实时通信,ClientA向ClientB发送字符1,ClientB向ClientA发送字符2。
#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <sys/un.h>#include <sys/time.h>#include <sys/ioctl.h>#include <unistd.h>#include <netinet/in.h>int main(){ int rcd ; struct sockaddr_un server_sockaddr ; int backlog ; ushort ci ; int watch_fd_list[3] ; fd_set catch_fd_set ; fd_set watchset ; int new_cli_fd ; int maxfd; int socklen ,server_len; struct sockaddr_un cli_sockaddr ; struct { char module_id ; /* Module ID */ int cli_sock_fd ; /* Socket ID */ } cli_info_t[2] ; for (ci=0;ci<=1;ci++) cli_info_t[ci].cli_sock_fd=-1; for (ci=0;ci<=2;ci++) watch_fd_list[ci]=-1; int server_sockfd,client_sockfd; server_sockfd = socket( AF_UNIX, SOCK_STREAM, 0 ) ; server_sockaddr.sun_family = AF_UNIX ; strcpy( server_sockaddr.sun_path, "server_socket" ) ; server_len=sizeof(server_sockaddr); rcd = bind( server_sockfd, ( struct sockaddr * )&server_sockaddr, server_len ) ; backlog = 5 ; rcd = listen( server_sockfd, backlog ) ; printf("SERVER::Server is waitting on socket=%d \n",server_sockfd); watch_fd_list[0]=server_sockfd; FD_ZERO( &watchset ) ; FD_SET( server_sockfd, &watchset ) ; maxfd=watch_fd_list[0];
在上面的程序中,Server生成listening Socket(server_sockfd),初始化Socket监视集合(watchset),并将listening Socket放入Socket监视集合中。
while (1){char ch;int fd;int nread;catch_fd_set=watchset;rcd = select( maxfd+1, &catch_fd_set, NULL, NULL, (struct timeval *)0 ) ;
在上面的程序中,Server运用系统调用函数 select来实时检查是否有数据到达Socket监视集合中的任何一个socket。
if ( rcd < 0 ) { printf("SERVER::Server 5 \n"); exit(1); } if ( FD_ISSET( server_sockfd, &catch_fd_set ) ) { socklen = sizeof( cli_sockaddr ) ; new_cli_fd = accept( server_sockfd, ( struct sockaddr * ) &( cli_sockaddr ), &socklen ) ; printf(" SERVER::open communication with Client %s on socket %d\n", cli_sockaddr.sun_path,new_cli_fd); for (ci=1;ci<=2;ci++){ if(watch_fd_list[ci] != -1) continue; else{ watch_fd_list[ci] = new_cli_fd; break; } } FD_SET(new_cli_fd , &watchset ) ; if ( maxfd < new_cli_fd ) { maxfd = new_cli_fd ; } for ( ci=0;ci<=1;ci++){ if(cli_info_t[ci].cli_sock_fd == -1) { cli_info_t[ci].module_id=cli_sockaddr.sun_path[0]; cli_info_t[ci].cli_sock_fd=new_cli_fd; break; } } continue; }
在上面的程序中,Server运用系统调用函数FD_ISSET来检查是否有客户端的连接请求到达Listening Socket, 如果返回值大于0,Server生成一个新的通信Socket (new_cli_fd)与客户端连接。将新生成的通信Socket放入Socket监视集合中(FD_SET)。将客户端的信息(ID号和Socket描述号)保存在注册表cli_info_t中
for ( ci = 1; ci<=2 ; ci++ ) { int dst_fd = -1 ; char dst_module_id; char src_module_id; int i; if (watch_fd_list[ ci ]==-1) continue; if ( !FD_ISSET( watch_fd_list[ ci ], &catch_fd_set ) ) { continue ; } ioctl(watch_fd_list[ ci ],FIONREAD,&nread); if (nread==0){ continue; } read( watch_fd_list[ ci ], &dst_module_id, 1 ) ; for (i=0;i<=1;i++){ if(cli_info_t[i].module_id == dst_module_id) dst_fd= cli_info_t[i].cli_sock_fd; if(cli_info_t[i].cli_sock_fd==watch_fd_list[ ci ]) src_module_id= cli_info_t[i].module_id; } read( watch_fd_list[ ci ], &ch, 1 ) ; printf("SERVER::char=%c to Client %c on socket%d\n",ch, dst_module_id,dst_fd); write(dst_fd,&src_module_id, 1 ) ; write(dst_fd,&ch, 1 ) ; } } }
在上面的程序中,如果有数据到达某个通信Socket,Server则读出数据并取出收信客户端ID号。在ID登记表中找到收信客户端对应的Socket描述号。并将数据通过对应Socket传送到收信客户端
给出客户端 ClientA的程序
ClientB的程序只需将 char dst_module_id='B'; 改为char dst_module_id='A';char ch='1'; 改为char char ch='2';既可。
#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <sys/un.h>#include <unistd.h>#include <netinet/in.h>#include <arpa/inet.h>int main(){int client_sockfd;int len;struct sockaddr_un server_sockaddr,cli_sockaddr;int result;char dst_module_id='B';char ch='1';char src_module_id;client_sockfd= socket(AF_UNIX,SOCK_STREAM,0);cli_sockaddr.sun_family = AF_UNIX ;strcpy( cli_sockaddr.sun_path, "A" ) ;bind(client_sockfd,(struct sockaddr * )&cli_sockaddr, sizeof( cli_sockaddr ) ) ;server_sockaddr.sun_family=AF_UNIX;strcpy( server_sockaddr.sun_path, "server_socket" ) ;len=sizeof(server_sockaddr);result = connect(client_sockfd,( struct sockaddr * )&server_sockaddr,len);if (result <0){printf("ClientA::error on connecting \n");exit(1);}printf("ClientA::succeed in connecting with server\n");sleep(10);write(client_sockfd,&dst_module_id,1);write(client_sockfd,&ch,1); read (client_sockfd,&src_module_id,1); read (client_sockfd,&ch,1); printf("ClientA::char from Client %c =%c\n", src_module_id,ch);close (client_sockfd);}
下面是样本程序的执行结果
[root@zhou test]# ./server &[3] 4301[root@zhou test]# SERVER::Server is waitting on socket=3./clientA & ./clientB &[4] 4302[5] 4303ClientA::succeed in connecting with server SERVER::open communication with Client A on socket 4[root@zhou test]# SERVER::open communication with Client B on socket 5ClientB::succeed in connecting with serverSERVER::char=1 to Client B on socket5ClientB::char from Client A =1SERVER::char=2 to Client A on socket4ClientA::char from Client B =2
程序清单下载:server.c,clientA.c
#include <sys/types.h>#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
int main()
{
int rcd ;
struct sockaddr_un server_sockaddr ;
int backlog ;
ushort ci ;
int watch_fd_list[3] ;
fd_set catch_fd_set ;
fd_set watchset ;
int new_cli_fd ;
int maxfd;
int socklen,server_len;
struct sockaddr_un cli_sockaddr ;
struct {
char module_id ; /* Module ID */
int cli_sock_fd ; /* Socket ID */
} cli_info_t[2] ;
for (ci=0;ci<=1;ci++)
cli_info_t[ci].cli_sock_fd=-1;
for (ci=0;ci<=2;ci++)
watch_fd_list[ci]=-1;
int server_sockfd,client_sockfd;
server_sockfd = socket( AF_UNIX, SOCK_STREAM, 0 ) ;
server_sockaddr.sun_family = AF_UNIX ;
// server_sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
// server_sockaddr.sin_port=htons(9734);
strcpy( server_sockaddr.sun_path, "server_socket" ) ;
server_len=sizeof(server_sockaddr);
rcd = bind( server_sockfd, ( struct sockaddr * )&server_sockaddr, server_len ) ;
backlog = 5 ;
rcd = listen( server_sockfd, backlog ) ;
printf("SERVER::Server is waitting on socket=%d \n",server_sockfd);
watch_fd_list[0]=server_sockfd;
FD_ZERO( &watchset ) ;
FD_SET( server_sockfd, &watchset ) ;
maxfd=watch_fd_list[0];
while (1){
char ch;
int fd;
int nread;
catch_fd_set=watchset;
rcd = select( maxfd+1, &catch_fd_set, NULL, NULL, (struct timeval *)0 ) ;
if ( rcd < 0 ) {
printf("SERVER::Server 5 \n");
exit(1);
}
if ( FD_ISSET( server_sockfd, &catch_fd_set ) ) {
socklen = sizeof( cli_sockaddr ) ;
new_cli_fd = accept( server_sockfd, ( struct sockaddr * )&( cli_sockaddr ), &socklen ) ;
printf(" SERVER::open communication with Client %s on socket %d\n", cli_sockaddr.sun_path,new_cli_fd);
for (ci=1;ci<=2;ci++){
if(watch_fd_list[ci] != -1) continue;
else{
watch_fd_list[ci] = new_cli_fd;
break;
}
}
FD_SET(new_cli_fd , &watchset ) ;
if ( maxfd < new_cli_fd ) {
maxfd = new_cli_fd ;
}
for ( ci=0;ci<=1;ci++){
if(cli_info_t[ci].cli_sock_fd == -1) {
cli_info_t[ci].module_id=cli_sockaddr.sun_path[0];
cli_info_t[ci].cli_sock_fd=new_cli_fd;
break;
}
}
----------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(){
int client_sockfd;
int len;
struct sockaddr_un server_sockaddr,cli_sockaddr;
int result;
char dst_module_id='B';
char ch='1';
char src_module_id;
client_sockfd= socket(AF_UNIX,SOCK_STREAM,0);
// memset( &cli_sockaddr, 0, sizeof( cli_sockaddr) ) ;
cli_sockaddr.sun_family = AF_UNIX ;
strcpy( cli_sockaddr.sun_path, "A" ) ;
bind( client_sockfd, ( struct sockaddr * )&cli_sockaddr, sizeof( cli_sockaddr ) ) ;
server_sockaddr.sun_family=AF_UNIX;
// server_sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
// server_sockaddr.sin_port=htons(9734);
strcpy( server_sockaddr.sun_path, "server_socket" ) ;
len=sizeof(server_sockaddr);
result = connect(client_sockfd,( struct sockaddr * )&server_sockaddr,len);
if (result <0){
printf("ClientA::error on connecting \n");
// exit(1);
}
printf("ClientA::succeed in connecting with server\n");
sleep(10);
write(client_sockfd,&dst_module_id,1);
write(client_sockfd,&ch,1);
read (client_sockfd,&src_module_id,1);
read (client_sockfd,&ch,1);
printf("ClientA::char from Client %c =%c\n", src_module_id,ch);
close (client_sockfd);
}
- linux多进程编程
- Linux多进程相关内容
- linux 多进程编程
- linux多进程调试
- linux GTK 多进程
- linux 多进程知识
- Linux多进程编程
- linux 多进程代码
- linux--多进程
- linux 多进程入门
- linux多进程
- linux 多进程 缺点
- linux 多进程编程
- linux 多进程编程
- linux多进程编程
- linux多进程多线程
- linux多进程编程
- Linux 多进程学习
- hihocoder1041
- 迭代器模式(四):使用内部类实现迭代器
- hdu 5023 A Corrupt Mayor's Performance Art(线段树区间合并)
- Brief overview of backward and forward
- intent.putExtra()方法参数详解
- Linux多进程
- 一个ACM渣渣关于找工作的胡扯
- JavaScript-初探
- void*相关小知识点
- PAT训练(5)
- LeetCode Binary Tree Paths
- java对redis的基本操作
- 欢迎使用CSDN-markdown编辑器
- 【BZOJ 3884】上帝与集合的正确用法