深入探索C语言中的各种Sleep方法

2024-09-01 10:04

本文主要是介绍深入探索C语言中的各种Sleep方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

引言

在程序设计中,有时需要让进程或线程暂停执行一段时间,这种需求可以通过使用 sleep 函数来实现。本文将详细介绍在 C 语言环境下可用的不同类型的 sleep 函数,包括它们的用途、参数以及注意事项,并提供一些示例代码。

目录

  1. 标准 sleep() 函数
    • 定义与原型
    • 参数解释
    • 返回值与错误处理
    • 示例代码
    • 底层原理
  2. 精确定时 nanosleep()
    • 定义与原型
    • 参数解析
    • 实现精确延时
    • 示例代码
    • 底层原理
  3. 非阻塞 usleep()
    • 定义与原型
    • 微秒级延迟
    • 使用场景与限制
    • 示例代码
    • 底层原理
  4. 线程专用 pthread_sleep()clock_nanosleep()
    • 线程间同步
    • 参数与用法
    • 示例代码
    • 底层原理
  5. 条件变量等待 pthread_cond_timedwait()
    • 条件变量与定时等待
    • 参数解析
    • 示例代码
    • 底层原理
  6. 总结与建议
  7. 参考资料

正文

1. 标准 sleep() 函数

定义与原型:

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

参数解释:

  • seconds: 指定要挂起进程的秒数。

返回值与错误处理:

  • 成功时返回实际睡眠的秒数。
  • 失败时返回 0 并将 errno 设置为 EINTR(如果进程被信号中断)。

示例代码:

#include <stdio.h>
#include <unistd.h>int main(void) {printf("Sleeping for 5 seconds...\n");if (sleep(5) == 0) {perror("Error sleeping");}printf("Woke up.\n");return 0;
}

底层原理:

  • sleep() 是一个系统调用,它通过发送 SIGTSTP 信号给当前进程来挂起进程。
    • 当进程调用 sleep() 函数时,它实际上是在调用内核中的一个系统调用处理程序。
    • 系统调用处理程序会检查进程是否已经准备好进入睡眠状态,并将进程的状态标记为不可中断的睡眠状态 (D)。
    • 内核会将进程添加到睡眠队列中,并开始计时。
    • 当指定的时间过去后,内核会唤醒该进程,并将进程的状态从睡眠状态改为可运行状态 ®。
    • 进程回到用户空间后,会继续执行。
    • 如果在睡眠过程中接收到一个信号(如 SIGALRM 或 SIGTERM),则进程会被立即唤醒,sleep() 函数会返回 0 并将 errno 设置为 EINTR。
    • sleep() 函数的精度相对较低,因为它只能以整秒为单位进行延时,不适合需要更高精度的应用场景。
    • 由于 sleep() 依赖于信号机制,因此在高负载情况下可能会出现延迟唤醒的情况。
    • 使用 sleep() 时需要注意避免被信号中断的问题,尤其是当需要精确控制延时时。
2. 精确定时 nanosleep()

定义与原型:

#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);

参数解析:

  • req: 请求的持续时间。
  • rem: 剩余的时间(如果请求的睡眠时间超过实际的睡眠时间)。

实现精确延时:
可以精确到纳秒级别。

示例代码:

#include <stdio.h>
#include <time.h>int main(void) {struct timespec ts, rem;ts.tv_sec = 1; // 秒ts.tv_nsec = 500000000; // 纳秒printf("Sleeping for 1.5 seconds...\n");if (nanosleep(&ts, &rem) != 0) {perror("Error in nanosleep");} else {printf("Woke up.\n");}return 0;
}

底层原理:

  • nanosleep() 使用的是基于时钟的定时机制,而不是简单的信号机制。
    • 当调用 nanosleep() 时,内核会检查 req 参数中的时间值,并开始计时。
    • 内核使用一个高精度的时钟来计时,并在达到指定时间时唤醒进程。
    • 如果进程在睡眠期间被信号中断,nanosleep() 会将剩余的睡眠时间保存在 rem 结构体中。
    • nanosleep() 支持中断处理,当进程被信号中断时,剩余的睡眠时间会保存在 rem 结构体中,可以在后续调用中使用。
    • 由于 nanosleep() 可以精确到纳秒,因此它非常适合需要高精度延时的应用场景。
    • nanosleep() 函数的实现依赖于内核提供的高精度时钟源,这使得它可以提供比 sleep() 更高的精度。
    • 在现代操作系统中,nanosleep() 使用的是实时时钟,这意味着即使系统处于睡眠状态,计时也不会受到影响。
    • 使用 nanosleep() 时需要注意,尽管它可以提供较高的精度,但在高负载情况下也可能受到一定的影响。
3. 非阻塞 usleep()

定义与原型:

#include <unistd.h>
int usleep(useconds_t usec);

微秒级延迟:
适用于需要更精细控制的情况。

使用场景与限制:

  • 微秒级别的精度。
  • 注意可能存在的不精确性。

示例代码:

#include <stdio.h>
#include <unistd.h>int main(void) {printf("Sleeping for 500 milliseconds...\n");if (usleep(500000) != 0) {perror("Error in usleep");} else {printf("Woke up.\n");}return 0;
}

底层原理:

  • usleep() 通常不是通过直接的系统调用来实现的,而是通过其他延时机制,如 select() 或者 poll() 来模拟实现。
    • usleep() 的实现依赖于底层操作系统的实现细节。
    • 在某些实现中,usleep() 可能会使用 select() 函数来实现延时功能。
    • select() 函数允许进程在一个或多个文件描述符上等待事件的发生,这里可以设置一个空的文件描述符列表,并设置一个延时时间。
    • 当延时时间到达后,select() 会返回,从而结束 usleep() 的调用。
    • 由于 usleep() 不是标准的系统调用,所以它的实现可能会因操作系统而异。
    • usleep() 的主要问题是它可能受到系统负载的影响,导致实际的延时时间比预期的长。
    • 在某些情况下,usleep() 可能会使用 poll() 函数来实现,这取决于操作系统的具体实现。
    • 使用 usleep() 时需要注意,虽然它可以提供微秒级别的精度,但实际延时时间可能会受到系统负载和其他因素的影响。

在这里插入图片描述

4. 线程专用 pthread_sleep()clock_nanosleep()

线程间同步:
专门用于线程的延时函数。

参数与用法:

  • clock_nanosleep() 更加灵活且具有更好的性能。

示例代码:

#include <stdio.h>
#include <pthread.h>
#include <time.h>void* thread_func(void *arg) {struct timespec ts;ts.tv_sec = 2; // 秒ts.tv_nsec = 0; // 纳秒printf("Thread: Sleeping for 2 seconds...\n");clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL);printf("Thread: Woke up.\n");pthread_exit(NULL);
}int main(void) {pthread_t thread;if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {perror("Error creating thread");return 1;}if (pthread_join(thread, NULL) != 0) {perror("Error joining thread");return 1;}return 0;
}

底层原理:

  • pthread_sleep()clock_nanosleep() 都是为了支持多线程环境中的延时操作而设计的。
    • clock_nanosleep() 使用的是线程的调度器,而不是整个进程的调度器。
    • 当一个线程进入 clock_nanosleep() 后,其他线程仍然可以继续运行。
    • clock_nanosleep() 提供了更高的精度,并且不会影响到其他线程的执行。
    • 内核维护了一个高精度的时钟,当线程调用 clock_nanosleep() 时,内核会记录下线程需要睡眠的时间,并在达到这个时间时唤醒线程。
    • 由于 clock_nanosleep() 是针对单个线程的操作,因此它不会影响到其他线程的调度。
    • clock_nanosleep() 使用的时钟源通常是实时时钟,这意味着即使系统处于睡眠状态,计时也不会受到影响。
    • 使用 clock_nanosleep() 时需要注意,尽管它可以提供较高的精度,但在高负载情况下也可能受到一定的影响。
5. 条件变量等待 pthread_cond_timedwait()

条件变量与定时等待:
结合条件变量进行定时等待,常用于多线程同步。

示例代码:

#include <stdio.h>
#include <pthread.h>
#include <time.h>int main(void) {pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int ready = 0;void *thread_func(void *arg) {struct timespec ts;ts.tv_sec = time(NULL) + 5; // 当前时间加上5秒ts.tv_nsec = 0;pthread_mutex_lock(&mutex);while (!ready) {printf("Thread: Waiting until %ld seconds...\n", ts.tv_sec);if (pthread_cond_timedwait(&cond, &mutex, &ts) != 0) {perror("Error in pthread_cond_timedwait");break;}}printf("Thread: Ready flag is set.\n");pthread_mutex_unlock(&mutex);pthread_exit(NULL);}pthread_t thread;if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {perror("Error creating thread");return 1;}sleep(3); // 等待3秒后设置标志pthread_mutex_lock(&mutex);ready = 1;pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);if (pthread_join(thread, NULL) != 0) {perror("Error joining thread");return 1;}return 0;
}

底层原理:

  • pthread_cond_timedwait() 用于在条件变量上等待,直到满足某个条件或者超时为止。
    • 当线程调用 pthread_cond_timedwait() 时,它会释放互斥锁并进入等待状态。
    • 如果条件满足或者到达了指定的时间,线程就会被唤醒并重新获取互斥锁。
    • 使用条件变量可以有效地同步多个线程的执行顺序。
    • 当一个线程调用 pthread_cond_timedwait() 时,内核会检查当前时间与请求的时间,如果当前时间未达到请求的时间,线程会被加入到条件变量的等待队列中。
    • 内核会定期检查条件变量队列中的线程是否可以被唤醒。
    • 当条件满足时,调用 pthread_cond_signal()pthread_cond_broadcast() 会唤醒一个或所有等待的线程。
    • 被唤醒的线程会重新获得互斥锁并继续执行。
    • 使用 pthread_cond_timedwait() 时需要注意,尽管它可以提供精确的定时等待,但在高负载情况下也可能受到一定的影响。
6. 总结与建议
  • 选择合适的函数: 根据精度要求和使用场景选择合适的延时函数。
  • 注意中断处理: 考虑到信号中断的可能性。
  • 测试与验证: 在实际应用中进行充分的测试以确保正确性。
  • 考虑系统负载: 在高负载情况下,即使是精确的延时函数也可能出现偏差。

这篇关于深入探索C语言中的各种Sleep方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126628

相关文章

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

使用jenv工具管理多个JDK版本的方法步骤

《使用jenv工具管理多个JDK版本的方法步骤》jenv是一个开源的Java环境管理工具,旨在帮助开发者在同一台机器上轻松管理和切换多个Java版本,:本文主要介绍使用jenv工具管理多个JD... 目录一、jenv到底是干啥的?二、jenv的核心功能(一)管理多个Java版本(二)支持插件扩展(三)环境隔

Java中Map.Entry()含义及方法使用代码

《Java中Map.Entry()含义及方法使用代码》:本文主要介绍Java中Map.Entry()含义及方法使用的相关资料,Map.Entry是Java中Map的静态内部接口,用于表示键值对,其... 目录前言 Map.Entry作用核心方法常见使用场景1. 遍历 Map 的所有键值对2. 直接修改 Ma

Mybatis Plus Join使用方法示例详解

《MybatisPlusJoin使用方法示例详解》:本文主要介绍MybatisPlusJoin使用方法示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录1、pom文件2、yaml配置文件3、分页插件4、示例代码:5、测试代码6、和PageHelper结合6

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个