第十二章 线程控制
关于习题12.1的思考
</pre><pre name="code" class="cpp">#include "apue.h"
#include <pthread.h>
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
void prepare() {
printf("preparing locks....%d \n", getpid());
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);
}
void parent() {
printf("parent unlocking locks.%d \n", getpid());
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
}
void child() {
printf("child unlcoking locks. %d\n", getpid());
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
}
void * thr_fn(void *arg) {
printf("this is a new thread %d \n", getpid());
pause();
return (0);
}
int main() {
pid_t pid;
pthread_t tid;
pthread_atfork(prepare, parent, child);
pthread_create(&tid, NULL, thr_fn, NULL);
sleep(2);
printf("parent fork ..%d\n", getpid());
if ((pid = fork()) < 0) {
return -1;
} else if (pid == 0) {
printf("child return from fork %d\n", getpid());
} else {
printf("parent return from fork %d\n", getpid());
}
return 0;
}
编译后执行两次,一次直接数据,一次通过管道重定向到文件
[AdvancedProgramingInUnixEnviroment]$ ./1217
this is a new thread 31855
parent fork ..31855
preparing locks....31855
parent unlocking locks.31855
parent return from fork 31855
child unlcoking locks. 31868
child return from fork 31868
[AdvancedProgramingInUnixEnviroment]$ rm file
[AdvancedProgramingInUnixEnviroment]$ ./1217 > file
[AdvancedProgramingInUnixEnviroment]$ cat file
this is a new thread 32051
parent fork ..32051
preparing locks....32051
parent unlocking locks.32051
parent return from fork 32051
this is a new thread 32051
parent fork ..32051
preparing locks....32051
child unlcoking locks. 32068
child return from fork 32068
猜测,在重定向文件输出时,fork后,相当于父进程和子进程两个进程,都会执行输出一次,并且先后重定向到文件中。
而普通情况情况下,都在一个流中直接输出。
为了简便分析,用一个简单的小程序代替上述程序
#include "apue.h"
#include <pthread.h>
int main()
{
pid_t pid;
printf("prepare fork %d\n", getpid());
if ((pid = fork()) < 0) {
return -1;
} else if (pid == 0) {
printf("this is child %d\n", getpid());
sleep(100);
} else {
printf ("this is parent %d\n", getpid());
sleep(100);
/*pid = fork();
if (pid == 0) {
printf ("this is another child %d\n", getpid());
} else {
printf ("this is always parent %d\n", getpid());
}*/
}
return 0;
}
利用linux 的strace命令,可以跟踪一个命令的执行,了解其细节,通过该命令分析。可以发现wirte命令的确不一样,前者是每次write一次到fd 1也就是标准输出上,而后者只write一次。
第一次
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f09d29cc000
write(1, "prepare fork 61180\n", 19prepare fork 61180
) = 19
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f09d29b3770) = 61181
write(1, "this is parent 61180\n", 21this is parent 61180
) = 21
this is child 61181
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f09d29b3770) = 61182
write(1, "this is always parent 61180\n", 28this is another child 61182
this is always parent 61180
) = 28
munmap(0x7f09d29cc000, 4096) = 0
第二次
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3dfd604000
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3dfd5eb770) = 1525
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3dfd5eb770) = 1526
write(1, "prepare fork 1524\nthis is parent"..., 65) = 65
munmap(0x7f3dfd604000, 4096)
接下来看两次执行时,每个线程的输出流的情况,第一次如下
[ AdvancedProgramingInUnixEnviroment]$ ./forktest &
[1] 22334
[AdvancedProgramingInUnixEnviroment]$ prepare fork 22334
this is parent 22334
this is child 22335
[AdvancedProgramingInUnixEnviroment]$
[lijingyi01@cp01-rdqa-dev401.cp01.baidu.com AdvancedProgramingInUnixEnviroment]$ ps -ef | grep fork
1026 22334 54923 0 17:44 pts/6 00:00:00 ./forktest
1026 22335 22334 0 17:44 pts/6 00:00:00 ./forktest
1026 22521 54923 0 17:44 pts/6 00:00:00 grep fork
[AdvancedProgramingInUnixEnviroment]$ ll /proc/22334/fd
total 0
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:44 0 -> /dev/pts/6
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:44 1 -> /dev/pts/6
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:44 2 -> /dev/pts/6
[AdvancedProgramingInUnixEnviroment]$ ll /proc/22335/fd
total 0
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:45 0 -> /dev/pts/6
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:45 1 -> /dev/pts/6
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:44 2 -> /dev/pts/6
第二次
[ AdvancedProgramingInUnixEnviroment]$ ./forktest > file4 &
[1] 36359
[AdvancedProgramingInUnixEnviroment]$ ps -ef | grep fork
1026 36359 54923 0 17:48 pts/6 00:00:00 ./forktest
1026 36360 36359 0 17:48 pts/6 00:00:00 ./forktest
1026 36385 54923 0 17:48 pts/6 00:00:00 grep fork
[AdvancedProgramingInUnixEnviroment]$ ll /proc/36360/fd
total 0
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:48 0 -> /dev/pts/6
l-wx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:48 1 -> /home/users/lijingyi01/work/AdvancedProgramingInUnixEnviroment/file4
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:48 2 -> /dev/pts/6
[AdvancedProgramingInUnixEnviroment]$ ll /proc/36359/fd
total 0
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:48 0 -> /dev/pts/6
l-wx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:48 1 -> /home/users/lijingyi01/work/AdvancedProgramingInUnixEnviroment/file4
lrwx------ 1 lijingyi01 lijingyi01 64 Oct 21 17:48 2 -> /dev/pts/6
可以看出,在重定向到文件时,主进程和子进程都会指定到对应的文件上,那么单个进程的所有输出已开始会缓存到一个buff中,然后都会打印到文件中一次。
但是奇怪的是
write(1, "prepare fork 1524\nthis is parent"..., 65) = 65
表示写往文件的是字符是长度是65,正好是打印三行的长度,而不是四行。
把程序中printf改为c++中的cout 之后endl清空流后,经过文件重定向后数据的结果就和原来一样了,所以现在清晰的结论就是,输出到shell中时是一个buffer,两个进程共用,输出后就刷新。而重定向到文件时,主进程已开始把第一句话放到输出buffer中,在fork时,这个buffer也被子进程复制,于是子进程中也有了第一句话,然后主进程和子进程所有的数据都到各自的buffer后,然后分别输出到文件中。