
Linux下的Sleep与Select:高效并发控制的艺术
在Linux系统编程中,处理并发任务和事件驱动编程时,`sleep`和`select`是两个不可或缺的系统调用
它们虽然看似简单,但在构建高效、响应迅速的应用程序时扮演着至关重要的角色
本文将深入探讨这两个机制的工作原理、使用场景以及它们如何协同工作以实现复杂的并发控制逻辑,旨在帮助开发者更好地理解和应用这些工具
一、sleep:时间等待的艺术
`sleep`命令是Linux中最基础的时间延迟机制之一,它使程序暂停执行指定的时间长度
这个命令不仅可以在shell脚本中使用,也可以通过系统调用`sleep()`在C/C++等编程语言中实现
`sleep`的时间单位可以是秒(默认)、分钟、小时或天,具体取决于传递给它的参数格式
1.1 基本用法
在shell中,`sleep`的基本用法如下:
sleep NUMBER【SUFFIX】
- `NUMBER`:等待的时间量
- `SUFFIX`:可选的时间单位,如`s`(秒,默认)、`m`(分钟)、`h`(小时)、`d`(天)
例如,让脚本暂停5秒:
sleep 5
在C语言中,`sleep()`函数定义在`
int sleep(unsigned int seconds);
该函数会使调用进程挂起指定的秒数
1.2 使用场景
`sleep`广泛应用于需要定时执行任务的场景,如:
- 轮询:定期检查某个条件是否满足,如文件是否存在、网络连接是否建立
- 延时启动:在启动服务或执行操作前等待一段时间,确保依赖条件已就绪
模拟:在测试或模拟中模拟延迟,观察系统行为
尽管`sleep`简单易用,但在高精度或需要同时处理多个事件的场景下,它显得力不从心
这时,`select`系统调用就显得尤为重要
二、select:多路复用I/O的基石
`select`系统调用是Linux下实现多路复用I/O(Multiplexed I/O)的关键机制之一,允许一个进程同时监视多个文件描述符(通常是套接字或管道),查看它们是否准备好进行读、写或发生异常
这种机制极大地提高了网络编程的效率,尤其是在处理大量并发连接时
2.1 基本原理
`select`的工作原理基于三个集合:读集合、写集合和异常集合
调用`select`时,进程会传入这三个集合的指针以及一个超时时间,然后`select`会阻塞(或超时后返回),直到至少有一个文件描述符就绪或超时发生
在C语言中,`select`函数定义如下:
include
include
include
int select(int nfds, fd_setreadfds, fd_set writefds, fd_setexceptfds, struct timeval timeout);
- `nfds`:监听的文件描述符数量中的最大值加1
- `readfds`:指向读文件描述符集合的指针
- `writefds`:指向写文件描述符集合的指针
- `exceptfds`:指向异常文件描述符集合的指针
- `timeout`:指定等待的超时时间,为`NULL`时`select`将无限等待
2.2 使用流程
使用`select`的一般步骤如下:
1.初始化文件描述符集合:使用FD_ZERO清空集合,`FD_SET`添加感兴趣的文件描述符
2.调用select:传入文件描述符集合和超时时间
3.检查返回结果:select返回就绪的文件描述符数量,使用`FD_ISSET`检查哪些文件描述符已就绪
2.3 示例代码
以下是一个简单的服务器使用`select`监听多个客户端连接的示例:
include
include
include
include
include
include
include
defineMAX_CLIENTS 100
defineBUFFER_SIZE 1024
int main() {
intserver_fd,new_socket;
structsockaddr_in address;
int addrlen = sizeof(address);
fd_set readfds;
charbuffer【BUFFER_SIZE】= {0};
// 创建套接字
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == {
perror(socketfailed);
exit(EXIT_FAILURE);
}
// 绑定地址和端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
if(bind(server_fd, (struct sockaddr)&address, sizeof(address))<0) {
perror(bindfailed);
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听连接
if(listen(server_fd, < {
perror(listen);
close(server_fd);
exit(EXIT_FAILURE);
}
// 将服务器套接字加入监听集合
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
while(1) {
fd_set rfds = readfds; // 复制当前监听集合
int retval =select(server_fd + 1, &rfds, NULL, NULL, NULL);
if(retval == -{
perror(select());
} else if(retval) {
if(FD_ISSET(server_fd, &rfds)) {
// 接受新连接
if((new_socket = accept(server_fd, (struct sockaddr)&address, (socklen_t)&addrlen))<{
perror(accept);
}else {
FD_SET(new_socket, &readfds); // 将新连接加入监听集合
printf(New connection , socket fd is %d , ip is : %s , port : %d n , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
}
}else {
// 处理已就绪的客户端连接
for(int i = 0; i < MAX_CLIENTS;i++){
if (FD_ISSET(i, &rfds)){
int valread =read(i, buffer,BUFFER_SIZE);
printf(Received: %s
, buffer);
send(i , Hello from server , strlen(Hello from server) , 0);
if(valread == {
// 连接关闭
getpeername(i ,(struc