如何使一个线程休眠/阻塞纳秒(或至少毫秒)?

12

如何在纳秒或毫秒级别上阻塞线程(或进程)?

请注意,无法使用sleep函数,因为其参数单位始终为秒。


2
你能解释一下为什么你需要这么细粒度的等待吗? - Jeff Foster
实现用户定义计时器的方法。 - RajSanpui
请查看 Stack Overflow 问题 *Timer function to provide time in nano seconds using C++*。 - dubnde
1
纳秒或毫秒,纳秒相当于大约2-3个CPU周期(假设为2-3 GHz),因此大约需要一到两个机器指令。nano/milli = 1/1,000,000,您应该确定您需要哪一个。 - Damon
呃,仅调用和从计时器函数返回会花费两打到500纳秒的时间(如果存在缓存和/或TLB未命中)。就目前而言,更好不一定意味着更好,因为更好并不意味着更精确。操作系统提供的等待/超时函数(参见R...的答案)是您可以合理获得的最佳选择。任何比这更细致的都是愚蠢的。 - Damon
可能是重复的问题,与 Timer function to provide time in nano seconds using C++ 相似。 - Peter Mortensen
7个回答

13

nanosleepclock_nanosleep是您应该使用的函数(后者允许您指定绝对时间而不是相对时间,并使用单调时钟或其他时钟,而不仅仅是实时时钟,如果运营商重置它可能会倒退)。

然而,请注意,就分辨率而言,您很少能获得比几微秒更好的效果,而且它总是将休眠持续时间向上舍入,而不是向下舍入。 (无论如何,向下舍入通常都是不可能的,因为在大多数机器上,进入和退出内核空间需要超过一微秒的时间。)

此外,如果可能,建议使用阻塞等待事件而不是睡眠一小段时间然后进行轮询的调用。例如,pthread_cond_waitpthread_cond_timedwaitsem_waitsem_timedwaitselectread等,这取决于您的线程执行的任务以及它如何与其他线程同步和/或与外部世界通信。


1
请注意,如果要实现计时器,您可能需要使用绝对时间(预先计算的下一个计时器到期时间)的 clock_nanosleep。使用相对时间,除非您采取额外的预防措施根据测量时间调整睡眠间隔,否则会累积误差。 - R.. GitHub STOP HELPING ICE
有趣的是,尽管使用现有的架构等待仅一个纳秒几乎是不可能的,但我的计算机时钟分辨率被称为1e-9。 - Alexis Wilke
@AlexisWilke:这并不意味着你可以等待一纳秒,只是报告的时间精度可以达到一纳秒。 - R.. GitHub STOP HELPING ICE

7

一种相对便携的方法是使用select()pselect()而不使用文件描述符:

void sleep(unsigned long nsec) {
    struct timespec delay = { nsec / 1000000000, nsec % 1000000000 };
    pselect(0, NULL, NULL, NULL, &delay, NULL);
}

任何具有 pselect 的系统几乎肯定也具有 nanosleep。它们都是由 POSIX 发明的,并且同时添加,我想是这样的。 - R.. GitHub STOP HELPING ICE
这是一个不错的代码,但是我们的CE-Linux支持nanosleep,然而为了清晰和未来的实现,你的代码很有用。谢谢。 - RajSanpui
这段代码不能直接编译。 - LotoLo
@LotoLo 添加了 struct,没有注意到它有 C 标签。 - Maxim Egorushkin
你也可以添加 ppoll(),它是在很久以前就有的 pselect()(我想应该还有 nanosleep())。 - Alexis Wilke

3
使用任何pthread的sleep变体,行为都不能保证。所有线程也可以休眠,因为内核不知道不同的线程。因此,需要一个解决方案,由pthread库处理而不是内核。
更安全和更清洁的解决方案是使用pthread_cond_timedwait...
pthread_mutex_t fakeMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t fakeCond = PTHREAD_COND_INITIALIZER;

void mywait(int timeInSec)
{
struct timespec timeToWait;
struct timeval now;
int rt;

gettimeofday(&now,NULL);

timeToWait.tv_sec = now.tv_sec + timeInSec;
timeToWait.tv_nsec = now.tv_usec*1000;

pthread_mutex_lock(&fakeMutex);
rt = pthread_cond_timedwait(&fakeCond, &fakeMutex, &timeToWait);
pthread_mutex_unlock(&fakeMutex);
printf("\nDone\n");
}

void* fun(void* arg)
{
printf("\nIn thread\n");
mywait(5);
}

int main()
{
pthread_t thread;
void *ret;

pthread_create(&thread, NULL, fun, NULL);
pthread_join(thread,&ret);
}

对于 pthread_cond_timedwait,您需要指定从当前时间开始等待多长时间。

现在通过使用 mywait() 函数只有调用它的线程会睡眠,而不会影响其他 pthreads。


内核非常了解线程,从内核的角度来看,它们只是共享内存和一些其他状态的独立进程。 - Douglas Leeder
这个答案的信息是错误的。sleep及其变体是针对调用线程而不是进程的。然而,使用pthread_cond_wait而不是睡眠函数的想法可能是一个非常好的想法,特别是如果微小的睡眠是为了轮询某些条件。 - R.. GitHub STOP HELPING ICE
@道格拉斯: 如果我没有记错的话,内核不知道用户级线程。这就是为什么,当一个用户级线程进行阻塞系统调用时,所有用户级线程都会被阻塞的原因。但对于内核级线程来说,情况并非如此。 - RajSanpui
@kingmasher1:你说得对。内核只知道内核线程的信息,它没有关于用户级线程的信息,因此出现了问题。 - Furquan

3

尝试使用usleep()。是的,这不会给你纳秒级精度,但微秒也可以工作 => 毫秒也可以。


有什么方法可以阻塞纳秒级别的时间?假设用户给出等待时间为:2.0000045,这将失败。 - RajSanpui
即使unix系统或其他系统支持纳秒,它也受到CPU速度的限制。让我们看看为什么。考虑一个2.5 GHz的处理器,在一秒钟内,它可以执行约10^8个处理器命令,这意味着每微秒约100个操作=>在该处理器上可以处理这种粒度,但是每纳秒0.1个操作=> 即使有这样的粒度支持,处理器也无法正确地测量时间。 - Mihran Hovsepyan
3
usleep函数在POSIX 2003标准中被废弃,且在2008年被移除。 - R.. GitHub STOP HELPING ICE

3

nanosleep可以让你指定睡眠的精度,最小单位可以到纳秒级别。但是由于内核/CPU的限制,实际的睡眠分辨率可能会更大。


1
在高端机器上,它可以以亚微秒的分辨率真正地休眠(虽然不会更小,但最短的休眠时间可能是500-900纳秒)。 - R.. GitHub STOP HELPING ICE

2

由于通常Linux发行版不是(硬)实时操作系统,因此在一般的Linux操作系统上实现精确的纳秒分辨率将是不可能的。如果您确实需要对时间进行精细控制,请考虑使用这样的操作系统。

维基百科在这里列出了一些实时操作系统:http://en.wikipedia.org/wiki/RTOS(请注意,它没有说明它们是软实时还是硬实时,因此您需要进行一些研究)。


我们没有使用市场上可用的商业Linux操作系统。我们使用自己的发行版。 - RajSanpui

1
在具有多个硬件定时器访问权限的嵌入式系统中,为您的纳秒或微秒等待创建高速时钟。创建一个宏来启用和禁用它,并在定时器中断服务例程中处理您的高分辨率处理。
如果浪费功率和忙等不是问题,请执行一些无操作指令-但请验证编译器不会优化您的无操作指令。尝试使用易失性类型。

延迟循环在现代高性能CPU(包括手机)上除了“等待至少几十纳秒”之外,无法用于其他任何用途,但可能会根据CPU频率处于空闲状态与最大睿频而增加5倍到10倍的时间。此外,NOP对延迟的贡献不是线性的,除非您在循环中使用足够多的NOP来使其在前端而不是后端1个时钟周期内取得瓶颈效应。另请参见如何在x86 Linux上计算汇编延迟循环的时间?以获取RDTSC截止期限自旋等待循环的信息。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接