同一个
文件
可以被多次
打开
,譬如在
一个
进程
中多次
打开
同一个
文件
、在
多个
不同的
进程
中
打开
同一个
文件
。
⚫
一个
进程
内多次 open
打开
同一个
文件
,那么会得到
多个
不同的
文件描述符
fd,同理在关闭
文件
的时候也需要调用 close 依次关闭各个
文件描述符
。
多次
打开
同一个
文件
测试代码 1
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
一直苦恼
一个
问题:shell编程中,当
一个
任务完成时,接下来可以同时有两个任务可以运行,这两个任务互不影响。所以想当第
一个
任务完成后,同时启动后面的两个任务,不知到shell中如何同时启动。
今天解决了:使用后台运行的方式 &。
举个例子说明比较好。
有
一个
总执行的all.sh,有三个任务shell,分别是aa.sh, bb.sh,cc.sh。
当运行玩aa.sh后,同时运行bb.
IPC是
进程
间通信(interprocess communication)的简称。传统上该术语描述的是运行在某
个操作系统之上的不同
进程
间各种消息传递(message passing)的方式。本书还讲述多种形式
的同步(synchronization),因为像共享内存区这样的较新式的通信需要某种形式的同步参与
......
代码如下: #!/bin/bash SEND_THREAD_NUM=13 tmp_fifofile=”/tmp/$$.fifo” # 脚本运行的当前
进程
ID号作为
文件
名 mkfifo “$tmp_fifofile” # 新建
一个
随机fifo管道
文件
exec 6<>”$tmp_fifofile” # 定义
文件描述符
6指向这个fifo管道
文件
rm $tmp_fifofile for ((i=0;i<$SEND_THREAD_NUM;i++));do echo # for循环 往 fifo管道
文件
中写入13个空行 done >&6 for i in `seq 100`;do # 100 次 fo
本文研究的主要是
Linux
通过匿名管道进行
进程
间通信的相关内容,具体介绍如下。
在前面,介绍了一种
进程
间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是
一个
信号值。这里将介绍另一种
进程
间通信的方式——匿名管道,通过它
进程
间可以交换更多有用的数据。
一、什么是管道
如果你使用过
Linux
的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|”来使用管道,但是管理的真正定义是什么呢?管道是
一个
进程
连接数据流到另
一个
进程
的通道,它通常是用作把
一个
进程
的输出通过管道连接到另
一个
进程
的输入。
举个例子,在shell中输入命令:ls -l | grep st
1,父
进程
listen,创建pipe(下面所有父子
进程
之间的通信都用该pipe)
2,父
进程
预fork n个子
进程
3,各个子
进程
accept(listenfd),即所有子
进程
竞争accept请求。由于listenfd是在fork之前就有的,所以所有子
进程
都可以访问到,不需用到“
进程
间
文件描述符
传递”问题;
4,子
进程
每accept到
一个
请求都告诉父
进程
,父
进程
把请求数加1;子
进程
没完成
一个
请求,父
进程
把请求数减1;当父
进程
发现请求数 >= 子
进程
数时,父
进程
创建新的子
进程
,并把子
进程
数加1(当然子
进程
数有个预先上限);当父
进程
发现子
进程
数大于请求数加1时,父
进程
杀死多余的子
进程
。
总的来说,思想是让子
进程
accept并处理请求,父
进程
通过子
进程
发来的信息控制请求数与子
进程
数之间的
关系
。
代码如下:
代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PRECHILD 5
#define MAXCHILD 50
#define BUFSIZE 4096
#define PIDPATH "pid"
#define head503 "HTTP/1.1 503 Service unavailable\r\n"
#define head404 "HTTP/1.1 404 Not Found\r\n"
#define head200 "HTTP/1.1 200 0K\n\rContent—Type: text/html\n\rContent—Length: "
int len503, len404, len200;
int fd1[2], fd2[2];
typedef struct {
pid_t pid;
char status; // 'n' means new request; 'f' means finish the request
} REPORT;
void answer(int listenfd)
int connfd;
char buf[BUFSIZE];
int count;
int pid = getpid();
struct sockaddr_in cliaddr;
int size = sizeof(cliaddr);
char comm;
REPORT rep;
rep.pid = pid;
while (1) {
connfd = accept(listenfd, (struct sockaddr *)&cliaddr,(socklen_t *)&size ); //子
进程
accept请求
rep.status = 'n';
if (write(fd1[1], &rep, sizeof(rep)) 0) {
//printf("%s", content);
if (write(connfd, content, count) != count) {
printf("write failed\n");
fclose(fp);
close(connfd);
rep.status = 'f';
if (write(fd1[1], &rep, sizeof(rep)) < 0) {//告诉父
进程
自己处理完了请求
perror("write pipe finish failed");
exit(-1);
if (read(fd2[0], &comm, 1) < 1) {//等待来自父
进程
的命令
perror("read pipe failed");
exit(-1);
//printf("[%d] reve %c from pa\n", pid, comm);
if (comm == 'e') { //收到exit命令
printf("[%d] exit\n", pid);
exit(-1);
else if (comm == 'c') { //收到继续accept的命令
printf("[%d] continue\n", pid);
else {
printf("[%d] comm : %c illeagle\n", pid, comm);
void usage()
printf("Usage: http-serv port\n");
int write_pid()
int fd;
if ((fd = open(PIDPATH, O_WRONLY | O_TRUNC | O_CREAT, S_IWUSR)) < 0){
perror("open pidfile faild");
return -1;
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(fd, F_SETLK, &lock) == -1) {
int err = errno;
perror("fcntl faild");
if (err == EAGAIN) {
printf("Another http-serv process is running now!\n");
return -1;
return 0;
void daemon_init()
//clear file creation mask;
umask(0);
//become a session leader
if (fork() != 0)
exit(-1);
if (setsid() < 0)
exit(-1);
//make sure can be never get the TTY control
if (fork() != 0)
exit(-1);
//may chdir here
int i;
for (i = 0; i < 1024; i++)
close(i);
* Attach file descriptors 0, 1, and 2 to /dev/null.
int fd0, fd1, fd2;
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
printf("init failed\n");
exit(-1);
main(int argc, char **argv)
int listenfd;
struct sockaddr_in servaddr;
pid_t pid;
if (argc != 2) {
usage();
return -1;
signal(SIGCHLD, SIG_IGN);
len200 = strlen(head200);
len404 = strlen(head404);
len503 = strlen(head503);
daemon_init(); //转为后台程序,如需打印调试,把这行注释掉
if (write_pid() < 0) //避免同时有
多个
该程序在运行
return -1;
if (pipe(fd1) < 0) {
perror("pipe failed");
exit(-1);
if (s_pipe(fd2) < 0) {
perror("pipe failed");
exit(-1);
int port = atoi(argv[1]);
//initialize servaddr and listenfd...
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, 1000);
int i;
for (i = 0; i < PRECHILD ; i++) { //父
进程
预fork 子
进程
if ((pid = fork()) < 0) {
perror("fork faild");
exit(3);
else if (pid == 0) {
answer(listenfd);
else {
printf("have create child %d\n", pid);
char e = 'e';
char c = 'c';
int req_num = 0;
int child_num = PRECHILD;
REPORT rep;
while (1) {
//printf("req_num = %d, child_num = %d\n", req_num, child_num);
if (read(fd1[0], &rep, sizeof(rep)) = child_num && child_num <= MAXCHILD) { //请求数过多,创建更多子
进程
if ((pid = fork()) (req_num + 1) && child_num > PRECHILD) {//子
进程
数过多,删除多余的子
进程
if (write(fd2[1], &e, sizeof(e)) < sizeof(e)) {
perror("pa write pipe failed");
exit(-2);
//printf("tell child exit\n");
child_num --;
else {
if (write(fd2[1], &c, sizeof(c)) < sizeof(c)) {//让子
进程
继续等待accept
perror("pa write pipe failed");
exit(-2);
//printf("tell child continue\n");
return 0;
利用fork()创建
多个
子
进程
11:09 pm on Oct 23rd 2010 greenMay
之间我学习了创建
一个
子
进程
,也大致理解了子
进程
与父
进程
的
关系
。今天无意间遇到
一个
创建
多个
子
进程
的问题,结果还发现了点小bug,现在写下来和大家分享。
我需要实现的目标如下:编写一段源程序,使系统调用fork()创建两个子
进程
,当此程序运行时,在系统中有
一个
父
进程
和两个子
进程
活动。让每
一个
进程
在屏幕上显示
一个
字符:父
进程
显示字符“a”;子
进程
分别显示字符“b”和字符“c”。
一开始我的主要代码如下:
view source
print?
01 int main()
03 pid_t child1;
04 pid_t child2;
05 child1 = fork();
06 child2 = fork();
07 if(child1 == 0)
08 {
09 printf("Child1:a\n");
10 return 0;
11 }
12 if(child2 == 0)
13 {
14 printf("Child2:b\n");
15 return 0;
16 }
17 else
18 {
19 waitpid(child1,NULL,0);
20 waitpid(child2,NULL,0);
21 printf("Parent:c\n");
22 }
23 return 0;
奇怪的是,我得到的是这样
一个
结果:
Child1:a
Child1:a
Child2:b
Parent:c
竟然有两个Child1。可是我的代码里明明只是让Chidl1打印一次啊。搜索到一篇好的博文。文章仔细分析了和我几乎相同的情况。事实上,是我的粗心和对fork()的理解不深刻导致了上述的奇怪问题。
我们知道,fork()之后,我们还是首先执行的是父
进程
,也就是如下代码段:
view source
print?
1 waitpid(child1,NULL,0);
2 waitpid(child2,NULL,0);
3 printf("Parent:c\n");
然后waitpid(child1,NULL,0),进入child1的执行。child1将要执行的是如下的的代码段:
view source
print?
1 child2 = fork();
2 if(child1 == 0)
4 printf("Child1:a\n");
5 return 0;
注意,第一行的那个child2 = fork()!这就意味着对于child1来说,它自己又要创建
一个
子
进程
,这时候他成为了父亲。这时候,它有
一个
儿子child2,但是这个child2不同与我们刚才定义的那个child2,这个child2其实是parent的孙子。之所以又打印了一边Child1。如果加上如下代码就明白了:
view source
print?
01 child2 = fork();
02 if(child1 == 0)
03 {
04 if(child2 == 0)
05 {
06 printf("GrandChild!\n");
07 }
08 printf("Child1:a\n");
09 return 0;
10 }
这时候将出现:
Child1:a
GrandChild!
Child1:a
Child2:b
Parent:c
恩,这就很明白了!我无意间多调用了一次child2=fork();
所以,如果要达到我最初的目的,需要改变child2的fork()的位置:
view source
print?
01 #include
02 #include
03 #include
04 #include
05 int main()
07 pid_t child1;
08 pid_t child2;
09 child1 = fork();
10 child2 = fork();
11 if(child1 == 0)
12 {
13 printf("Child1:a\n");
14 return 0;
15 }
16 if(child2 == 0)
17 {
18 printf("Child2:b\n");
19 return 0;
20 }
21 else
22 {
23 waitpid(child1,NULL,0);
24 waitpid(child2,NULL,0);
25 printf("Parent:c\n");
26 }
27 return 0;
我参照的那个博文最后给出了
一个
更为普遍的fork()创建多
进程
的程序框架:
view source
print?
01 pid_t create_child()
03 pid_t p = fork();
04 if( p == 0 )
05 {
06 printf("in child %d\n", getpid());
07 //do something
08 return 0;
09 }
10 return p;
12 int main(void)
14 pid_t p1 = create_child();
15 pid_t p2 = create_child();
17 int st1, st2;
18 waitpid( p1, &st1, 0);
19 waitpid( p2, &st2, 0);
20 printf("in parent, pid = %d\n", getpid());
21 printf("in parent, child 1 exited with %d\n", st1);
22 printf("in parent, child 2 exited with %d\n", st2);
23 return 0;
注意到,期中的create_child()函数最后有
一个
return p。这个return p将pid返回给了父
进程
,其实也是将子
进程
对于CPU的控制权交还给了父
进程
,这样就避免了
多个
子
进程
在创建之时互相影响了。
可以说,今天的这个问题真是
一个
有趣的事情。代码有的时候就是这么奇怪~
最后,向我引用的那篇文章致敬!
Linux
内核对多
进程
和多线程的支持方式:
线程机制支持并发程序设计技术,在多处理器上能真正保证并行处理。而在
linux
实现线程很特别,
linux
把所有的线程都当作
进程
实现。
linux
下线程看起来就像普通
进程
(只是该
进程
和其他
进程
共享资源,如地址空间)。上述机制与Microsoft windows或是Sun Solaris实现差异很大。
Linux
的线程实现是在核外进行的,核内提供的是创建
进程
的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不同的参数调用do_fork()核内API。 do_fork() 提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享
文件
系统信息)、CLONE_FILES(共享
文件描述符
表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享
进程
ID,仅对核内
进程
,即0号
进程
有效)。当使用fork系统调用产生多
进程
时,内核调用do_fork()不使用任何共享属性,
进程
拥有独立的运行环境。当使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的”
进程
”拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
即:
Linux
下不管是多线程编程还是多
进程
编程,最终都是用do_fork实现的多
进程
编程,只是
进程
创建时的参数不同,从而导致有不同的共享环境。
Linux
线程在核内是以轻量级
进程
的形式存在的,拥有独立的
进程
表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用
一个
管理线程(__pthread_manager() ,每个
进程
独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号,而主线程pthread_create()) 的调用者则通过管道将请求信息传给管理线程。
很多朋友都说使用多线程的好处是资源占用少,其隐含之意就是说
进程
占用资源比线程多,对吧?但实际上
Linux
下多
进程
是否就真的点用很多资源呢?暂且不说
进程
是否比线程占用资源多,就
进程
占用资源的多少情况而言,
Linux
确实是做得相当节省的。产生
一个
多
进程
时肯定是要产生的一点内存是要复制
进程
表项,即
一个
task_struct结构,但这个结构本身做得相当小巧。其它对于
一个
进程
来说必须有的数据段、代码段、堆栈段是不是全盘复制呢?对于多
进程
来说,代码段是肯定不用复制的,因为父
进程
和各子
进程
的代码段是相同的,数据段和堆栈段呢?也不一定,因为在
Linux
里广泛使用的
一个
技术叫copy-on-write,即写时拷贝。copy-on-write意味着什么呢?意味着资源节省,假设有
一个
变量x在父
进程
里存在,当这个父
进程
创建
一个
子
进程
或
多个
子
进程
时这个变量x是否复制到了子
进程
的内存空间呢?不会的,子
进程
和父
进程
使用
同一个
内存空间的变量,但当子
进程
或父
进程
要改变变量x的值时就会复制该变量,从而导致父子
进程
里的变量值不同。父子
进程
变量是互不影响的,由于父子
进程
地址空间是完全隔开的,变量的地址可以是完全相同的。
Linux
的”线程”和”
进程
”实际上处于
一个
调度层次,共享
一个
进程
标识符空间,这种限制使得不可能在
Linux
上实现完全意义上的POSIX线程机制,因此众多的
Linux
线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在功能上尽可能逼近。
Linux
进程
的创建是非常迅速的。内核设计与实现一书中甚至指出
Linux
创建
进程
的速度和其他针对线程优化的操作系统(Windows,Solaris)创建线程的速度相比,测试结果非常的好,也就是说创建速度很快。由于异步信号是内核以
进程
为单位分发的,而
Linux
Threads的每个线程对内核来说都是
一个
进程
,且没有实现”线程组”,因此,某些语义不符合POSIX标准,比如没有实现向
进程
中所有线程发送信号,README对此作了说明。
Linux
Threads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。
Linux
Threads 的问题,特别是兼容性上的问题,严重阻碍了
Linux
上的跨平台应用(如Apache)采用多线程设计,从而使得
Linux
上的线程应用一直保持在比较低的水平。在
Linux
社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。目前最为人看好的有两个项目,
一个
是RedHat公司牵头研发的NPTL(Native Posix Thread Library),另
一个
则是IBM投资开发的NGPT(Next Generation Posix Threading),二者都是围绕完全兼容POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了
Linux
Threads的缺点,且都是重起炉灶全新设计的。
综上所述的结论是在
Linux
下编程多用多
进程
编程少用多线程编程。
IBM有个家伙做了个测试,发现切换线程context的时候,windows比
linux
快一倍多。进出最快的锁(windows2k的 critical section和
linux
的pthread_mutex),windows比
linux
的要快五倍左右。当然这并不是说
linux
不好,而且在经过实际编程之后,综合来看我觉得
linux
更适合做high performance server,不过在多线程这个具体的领域内,
linux
还是稍逊windows一点。这应该是情有可原的,毕竟unix家族都是从多
进程
过来的,而 windows从头就是多线程的。
如果是UNIX/
linux
环境,采用多线程没必要。
多线程比多
进程
性能高?误导!
应该说,多线程比多
进程
成本低,但性能更低。
在UNIX环境,多
进程
调度开销比多线程调度开销,没有显著区别,就是说,UNIX
进程
调度效率是很高的。内存消耗方面,二者只差全局数据区,现在内存都很便宜,服务器内存动辄若干G,根本不是问题。
多
进程
是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。
多线程是平面交通系统,造价低,但红绿灯太多,老堵车。
我们现在都开跑车,油(主频)有的是,不怕上坡下坡,就怕堵车。
高性能交易服务器中间件,如TUXEDO,都是主张多
进程
的。实际测试表明,TUXEDO性能和并发效率是非常高的。TUXEDO是贝尔实验室的,与UNIX同宗,应该是对UNIX理解最为深刻的,他们的意见应该具有很大的参考意义
2010年7月10日08:43
回复 | 引用 | #1
文章很有深度,我们把握
一个
尺度就可以了,在windows下使用线程,unix下则使用
进程
就可以了
2010年9月9日13:49
回复 | 引用 | #2
错的太多了,博主,应该看看新资料了。
现在都2010年了,NPTL早就取代了老的
Linux
thread。而且通常多线程有性能优势,但是多
进程
更稳定,并且通常性能瓶颈不在于是
进程
模型还是线程模型而在于IO。
2010年9月9日13:56
回复 | 引用 | #3
关于那个critical section和pthread_mutex_t,critical section本质上是
一个
自旋锁,短期锁当然快,不知道你说的那个IBM的哥们怎么比的,要比也该是和pthread_spinlock_t比。
admin
2010年9月9日17:28
回复 | 引用 | #4
rjoo挺热心的,呵呵,这篇文章不是我写的,但有几个地方我可以解答一下:
1.
Linux
下没有线程的概念,pthread线程实质是通过轻量级
进程
实现的。你说瓶颈在IO,这一点我很赞同你的意见,作者如果能再写个IO操作的文章来的话就会更好了。
2. mutex和critical section的确是不能比的。
一个
涉及到内核,
一个
没有涉及到内核。呵呵,很佩服你对这些东西的掌握程度,有机会多交流。
2010年9月9日17:40
回复 | 引用 | #5
我们组的最近项目的经验告诉我们能用多
进程
不用多线程,多线程安全编程难,而且锁会早成效率很低,甚至不如单线程,你说的NPTL我知道,他只是多线程优化了并不能改变多线程安全编程的问题,锁的问题。谢谢指教,实践出真知。
@rjoo
2010年9月9日17:44
回复 | 引用 | #6
你说的锁,我确实不太了解,但是我们leader对它很了解,就是最近的
一个
项目,锁搞得他很郁闷,他也终于同意我的关键,尽可能不用多线程。
@rjoo
2010年9月29日13:41
回复 | 引用 | #7
@admin
Linux
下没有线程的概念,pthread线程实质是通过轻量级
进程
实现的—这是2.4内核以前的情况(实际上是2.0时引入的,那可实在是太久了),2.4内核引入NGPL,2.6内核线程支持改为NPTL。NPTL实现的是1:1的线程模型(有资料说Win也是这种实现,虽然不太确定,但我觉得可能性很大),而NGPT虽然是理论上最先进的m:n线程模型,但最后实现出来的性能差NPTL一大截,最后被抛弃。看看文中说法就知道要么文章写的很早,要么作者看了一堆十年前的资料。
给个链接:
http://www.kegel.com/c10k.html#threads.
linux
threads
finalday
2010年10月15日17:26
回复 | 引用 | #8
忍不住跳出来说,作者对并发编程的理解还不行。
比如说锁的问题,说得好像是多线程才需要的东西一样。如果
一个
应用多
进程
时完全不用锁,多线程也就多
一个
轻量级锁——锁一下,各回各家,每个线程用自己的专有存储,之后不就和多
进程
一样了?这样会被搞得很郁闷?当然不会。所以说明那个应用对于数据共享的需求不是这么简单,既然不是这么简单,多
进程
程序一样要加锁。多
进程
的加解锁代价可比多线程大得多了,共享数据和协作也麻烦多了。
多线程编程难不难?难,但这是由于并发本身的难度引起的。“锁”,“安全编程”不管是多线程还是多
进程
都一样会遇到。
多线程的最大优点是数据共享和协作方便。
多
进程
的最大优点是挂了
一个
进程
不会影响其他
进程
,资源也不会泄露,故比较能容忍程序员犯错。
至于两者裸奔比性能,真的没啥意义。