在学习之前首先我们要先理解什么是休眠与唤醒。
一、简介
休眠,在应用层中意味着一个线程或者进程主动让出CPU,不继续执行,直到某个条件被满足才继续执行。这就是休眠休眠的目的就是等待资源或时间。唤醒,指的是休眠中的线程被重新激活,继续执行后续的任务。
二、作用
在了解休眠与唤醒以后,那么休眠与唤醒的作用是什么?我们在什么情况下要用到休眠与唤醒。首先休眠与唤醒的主要作用是在任务或者线程之间协调资源的使用,避免不必要的CPU占用,提高程序的执行效率。
这边简单的举几个休眠与唤醒的场景
- 等待文件或网络 I/O 完成
- 线程间通过条件变量进行同步和通讯
- 等待用户输入事件
- 定时任务(通过休眠线程实现)
三、具体场景和实现步骤
这里举一个实际开发中的场景:HTTP 服务器的请求处理。,在一个简单的HTTP服务器中,当没有请求是,服务器线程会进入休眠状态以节省CPU资源,而当有请求到达时,服务器会唤醒处理该请求。
下面是以恶搞用C语言编写的简单的HTTP服务器示例,使用POSIX线程和条件变量实现请求的休眠与唤醒机制
首先是头文件,请求队列,宏定义的定义
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
// 请求队列
char request_queue[BUFFER_SIZE][BUFFER_SIZE];
int request_count = 0;
pthread_mutex_t mutex;
pthread_cond_t request_available;
处理请求的线程函数
// 处理请求的线程函数
void* request_handler(void* arg) {
while (1) {
pthread_mutex_lock(&mutex); // 上锁
// 如果没有请求,休眠
while (request_count == 0) {
printf("没有请求,处理线程休眠...\n");
pthread_cond_wait(&request_available, &mutex);
}
// 处理请求
char request[BUFFER_SIZE];
strcpy(request, request_queue[--request_count]); // 取出请求
printf("处理请求:%s\n", request);
pthread_mutex_unlock(&mutex); // 解锁
// 模拟请求处理延迟
sleep(2);
}
}
首先当队列中没有请求时,会使用pthread_cond_wait()
进入休眠,当有新的请求时另一个线程通过pthread_cond_signal()
发出信号,唤醒休眠处理的线程
监听连接的线程函数
// 监听连接的线程函数
void* connection_listener(void* arg) {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket failed");
exit(EXIT_FAILURE);
}
// 限制端口复用
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定套接字
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// 开始监听
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("服务器启动,等待连接...\n");
while (1) {
// 等待连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("Accept failed");
continue;
}
// 读取请求
char buffer[BUFFER_SIZE] = {0};
read(new_socket, buffer, BUFFER_SIZE);
printf("接收到请求:%s\n", buffer);
pthread_mutex_lock(&mutex); // 上锁
// 将请求放入队列
strcpy(request_queue[request_count++], buffer);
// 唤醒处理线程
pthread_cond_signal(&request_available);
pthread_mutex_unlock(&mutex); // 解锁
close(new_socket); // 关闭套接字
}
}
首先使用IPv4和TCP协议创建一个套接字,然后定义一个定义一个 sockaddr_in
结构体,表示服务器的地址信息。使用bind绑定服务器的套接字文件描述符server_fd
和 address,将服务器绑定到指定的IP地址和端口上。
使用listen函数使服务器的套接字进入监听专题,准备接收客户端的连接请求。while循环,服务器会无限循环,持续监听客户端连接,不断处理客户端的请求。accept()
:服务器阻塞等待客户端的连接。当有客户端发起连接时,accept()
返回一个新的套接字 new_socket
,用于与客户端通信。然后使用read函数读取客户端发送的数据到buffer中。最后为了确保线程安全,在访问共享资源请求队列和请求技术时要加锁。
主函数
int main() {
pthread_t handler_thread, listener_thread;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&request_available, NULL);
// 创建处理请求的线程和监听连接的线程
pthread_create(&handler_thread, NULL, request_handler, NULL);
pthread_create(&listener_thread, NULL, connection_listener, NULL);
// 等待线程结束
pthread_join(handler_thread, NULL);
pthread_join(listener_thread, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&request_available);
return 0;
}
在主函数中主要是初始化互斥锁和条件变量,创建线程,调用pthread_join()
等待两个线程执行完。
四、整体代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
// 请求队列
char request_queue[BUFFER_SIZE][BUFFER_SIZE];
int request_count = 0;
pthread_mutex_t mutex;
pthread_cond_t request_available;
// 处理请求的线程函数
void* request_handler(void* arg) {
while (1) {
pthread_mutex_lock(&mutex); // 上锁
// 如果没有请求,休眠
while (request_count == 0) {
printf("没有请求,处理线程休眠...\n");
pthread_cond_wait(&request_available, &mutex);
}
// 处理请求
char request[BUFFER_SIZE];
strcpy(request, request_queue[--request_count]); // 取出请求
printf("处理请求:%s\n", request);
pthread_mutex_unlock(&mutex); // 解锁
// 模拟请求处理延迟
sleep(2);
}
}
// 监听连接的线程函数
void* connection_listener(void* arg) {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket failed");
exit(EXIT_FAILURE);
}
// 限制端口复用
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定套接字
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// 开始监听
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("服务器启动,等待连接...\n");
while (1) {
// 等待连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("Accept failed");
continue;
}
// 读取请求
char buffer[BUFFER_SIZE] = {0};
read(new_socket, buffer, BUFFER_SIZE);
printf("接收到请求:%s\n", buffer);
pthread_mutex_lock(&mutex); // 上锁
// 将请求放入队列
strcpy(request_queue[request_count++], buffer);
// 唤醒处理线程
pthread_cond_signal(&request_available);
pthread_mutex_unlock(&mutex); // 解锁
close(new_socket); // 关闭套接字
}
}
int main() {
pthread_t handler_thread, listener_thread;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&request_available, NULL);
// 创建处理请求的线程和监听连接的线程
pthread_create(&handler_thread, NULL, request_handler, NULL);
pthread_create(&listener_thread, NULL, connection_listener, NULL);
// 等待线程结束
pthread_join(handler_thread, NULL);
pthread_join(listener_thread, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&request_available);
return 0;
}