tzset()
分叉后调用似乎非常慢.如果我tzset()
在分叉之前首先调用父进程,我只会看到缓慢.我的TZ
环境变量未设置.我dtruss
是我的测试程序,它揭示了/etc/localtime
每个tzset()
调用的子进程读取,而父进程只读取一次.这个文件访问似乎是缓慢的来源,但我无法确定它每次在子进程中访问它的原因.
这是我的测试程序foo.c:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
void check(char *msg);
int main(int argc, char **argv) {
check("before");
pid_t c = fork();
if (c == 0) {
check("fork");
exit(0);
}
wait(NULL);
check("after");
}
void check(char *msg) {
struct timeval tv;
gettimeofday(&tv, NULL);
time_t start = tv.tv_sec;
suseconds_t mstart = tv.tv_usec;
for (int i = 0; i < 10000; i++) {
tzset();
}
gettimeofday(&tv, NULL);
double delta = (double)(tv.tv_sec - start);
delta += (double)(tv.tv_usec - mstart)/1000000.0;
printf("%s took: %fs\n", msg, delta);
}
Run Code Online (Sandbox Code Playgroud)
我编译并执行了foo.c,如下所示:
[muir@muir-work-mb scratch]$ clang -o foo foo.c
[muir@muir-work-mb scratch]$ env -i ./foo
before took: 0.002135s
fork took: 1.122254s
after took: 0.001120s
Run Code Online (Sandbox Code Playgroud)
我正在运行Mac OS X 10.10.1(也在10.9.5上复制).
我最初注意到ruby的缓慢(在子进程中Time#localtime slow).
Ken Thomases的回答可能是正确的,但我对一个更具体的答案感到好奇,因为我仍然发现单线程程序执行这样一个简单/常见操作后的慢速意外行为fork
.在检查了http://opensource.apple.com/source/Libc/Libc-997.1.1/stdtime/FreeBSD/localtime.c(不是100%确定这是正确的来源)后,我想我有一个答案.
该代码使用被动通知以确定是否当时区已经改为(相对于stat
荷兰国际集团/etc/localtime
每次).看来注册的通知令牌在子进程中变为无效fork
.此外,代码将错误视为使用无效令牌作为时区已更改的肯定通知,并且/etc/localtime
每次都继续读取.我想这是你可以获得的那种未定义的行为fork
?不过,如果库注意到错误并重新注册了通知,那将是很好的.
以下是localtime.c中的代码片段,它将错误值与状态值混合:
nstat = notify_check(p->token, &ncheck);
if (nstat || ncheck) {
Run Code Online (Sandbox Code Playgroud)
我证明了fork使用这个程序后注册令牌变得无效:
#include <notify.h>
#include <stdio.h>
#include <stdlib.h>
void bail(char *msg) {
printf("Error: %s\n", msg);
exit(1);
}
int main(int argc, char **argv) {
int token, something_changed, ret;
notify_register_check("com.apple.system.timezone", &token);
ret = notify_check(token, &something_changed);
if (ret)
bail("notify_check #1 failed");
if (!something_changed)
bail("expected change on first call");
ret = notify_check(token, &something_changed);
if (ret)
bail("notify_check #2 failed");
if (something_changed)
bail("expected no change");
pid_t c = fork();
if (c == 0) {
ret = notify_check(token, &something_changed);
if (ret) {
if (ret == NOTIFY_STATUS_INVALID_TOKEN)
printf("ret is invalid token\n");
if (!notify_is_valid_token(token))
printf("token is not valid\n");
bail("notify_check in fork failed");
}
if (something_changed)
bail("expected not changed");
exit(0);
}
wait(NULL);
}
Run Code Online (Sandbox Code Playgroud)
然后像这样运行:
muir-mb:projects muir$ clang -o notify_test notify_test.c
muir-mb:projects muir$ ./notify_test
ret is invalid token
token is not valid
Error: notify_check in fork failed
Run Code Online (Sandbox Code Playgroud)
你很幸运,没有遇到鼻恶魔!
\n\nPOSIX 规定,只有异步信号安全函数在fork()
调用函数之后和之前在子exec*()
进程中调用才是合法的。从标准(强调添加):
\n\n\n\xe2\x80\xa6 子进程只能执行异步信号安全操作,直到
\n\nexec
调用其中一个函数为止。\xe2\x80\xa6
\n\nPOSIX 程序员调用
\n\nfork()
. 原因之一是在同一个程序中创建一个新的控制线程(这最初只能在 POSIX 中通过创建一个新进程来实现);另一种是创建一个运行不同程序的新进程。在后一种情况下,调用后fork()
不久就会调用其中一个函数exec
。\n\n
fork()
在多线程世界中工作的普遍问题是如何处理所有线程。有两种选择。一是将所有线程复制到新进程中。这会导致程序员或实现处理在系统调用上挂起的线程,或者可能即将执行不应在新进程中执行的系统调用的线程。另一种选择是仅复制调用 的线程fork()
。这造成了进程本地资源的状态通常保存在进程内存中的困难。如果未调用的线程fork()
持有资源,则该资源永远不会在子进程中释放,因为其任务是释放该资源的线程在子进程中不存在。当程序员编写多线程程序时,第一个描述的使用
\nfork()
,即在同一程序中创建新线程,是由该pthread_create()
函数提供的。因此,该fork()
函数仅用于运行新程序,而调用在函数调用fork()
与函数调用之间需要某些资源的函数的效果exec
是未定义的。
这里和这里有异步信号安全函数的列表。对于任何其他功能,如果没有具体记录您要部署的平台上的实现添加了非标准安全保证,那么您必须认为它是不安全的,并且它在 to 的子端的fork()
行为未定义。