如何为类Shell程序添加调度任务并实现DLL插件化?
实现无干扰的定时调度器及插件化方案
一、实现不干扰主循环的定时调度
方案1:后台线程实现
- 思路:创建独立后台线程,线程内循环每隔5秒执行
task(),主循环继续处理用户输入,两者并行运行。 - 注意点:若
task()涉及共享资源(如全局变量、控制台输出),需加锁保证线程安全,避免竞态条件。 - 代码示例:
#include <iostream> #include <thread> #include <chrono> #include <mutex> std::mutex task_mutex; void task() { std::lock_guard<std::mutex> lock(task_mutex); std::cout << "定时任务执行中..." << std::endl; } void scheduler_thread() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); task(); } } int main() { std::thread scheduler(scheduler_thread); scheduler.detach(); while (true) { char c; std::cin >> c; if (c == 'a') { std::lock_guard<std::mutex> lock(task_mutex); task(); } else { std::cout << "输入字符:" << c << std::endl; } } return 0; }
方案2:IO多路复用(单线程)
- 思路:利用
select同时监听标准输入的可读事件和定时器超时事件。超时(5秒)时执行task(),有输入时处理用户输入,避免主循环阻塞在cin上。 - 优势:无需线程,避免线程安全问题,适合单线程场景。
- 代码示例(Linux):
#include <iostream> #include <sys/select.h> #include <unistd.h> void task() { std::cout << "定时任务执行中..." << std::endl; } int main() { fd_set read_fds; struct timeval timeout; while (true) { FD_ZERO(&read_fds); FD_SET(0, &read_fds); timeout.tv_sec = 5; timeout.tv_usec = 0; int ret = select(1, &read_fds, nullptr, nullptr, &timeout); if (ret == -1) { perror("select error"); break; } else if (ret == 0) { task(); } else { char c; std::cin >> c; if (c == 'a') { task(); } else { std::cout << "输入字符:" << c << std::endl; } } } return 0; }
Windows平台需先初始化Winsock库,调用
WSAStartup,也可使用WaitForSingleObject结合控制台输入句柄实现类似逻辑。
二、将调度器做成DLL插件
步骤1:定义DLL导出接口
先定义调度器对外接口,使用__declspec(dllexport)标记导出符号:
// scheduler_plugin.h #ifdef SCHEDULER_PLUGIN_EXPORTS #define SCHEDULER_API __declspec(dllexport) #else #define SCHEDULER_API __declspec(dllimport) #endif typedef void (*TaskCallback)(); SCHEDULER_API void InitScheduler(TaskCallback task); SCHEDULER_API void StartScheduler(); SCHEDULER_API void StopScheduler();
步骤2:实现DLL内部逻辑
用线程方案实现调度器,管理线程生命周期:
// scheduler_plugin.cpp #include "scheduler_plugin.h" #include <thread> #include <chrono> #include <atomic> #include <mutex> std::atomic<bool> g_running(false); std::thread g_scheduler_thread; TaskCallback g_task = nullptr; std::mutex g_task_mutex; void scheduler_loop() { while (g_running) { std::this_thread::sleep_for(std::chrono::seconds(5)); if (g_task != nullptr) { std::lock_guard<std::mutex> lock(g_task_mutex); g_task(); } } } SCHEDULER_API void InitScheduler(TaskCallback task) { std::lock_guard<std::mutex> lock(g_task_mutex); g_task = task; } SCHEDULER_API void StartScheduler() { if (!g_running) { g_running = true; g_scheduler_thread = std::thread(scheduler_loop); g_scheduler_thread.detach(); } } SCHEDULER_API void StopScheduler() { g_running = false; }
步骤3:主程序加载DLL并使用
主程序通过LoadLibrary加载DLL,获取函数指针调用接口:
#include <iostream> #include <windows.h> typedef void (*InitSchedulerFunc)(void(*)()); typedef void (*StartSchedulerFunc)(); typedef void (*StopSchedulerFunc)(); void my_task() { std::cout << "自定义定时任务执行中..." << std::endl; } int main() { HMODULE hDll = LoadLibrary(L"scheduler_plugin.dll"); if (hDll == nullptr) { std::cout << "加载DLL失败,错误码:" << GetLastError() << std::endl; return 1; } InitSchedulerFunc init_func = (InitSchedulerFunc)GetProcAddress(hDll, "InitScheduler"); StartSchedulerFunc start_func = (StartSchedulerFunc)GetProcAddress(hDll, "StartScheduler"); StopSchedulerFunc stop_func = (StopSchedulerFunc)GetProcAddress(hDll, "StopScheduler"); if (!init_func || !start_func || !stop_func) { std::cout << "获取DLL函数指针失败" << std::endl; FreeLibrary(hDll); return 1; } init_func(my_task); start_func(); while (true) { char c; std::cin >> c; if (c == 'a') { my_task(); } else if (c == 'q') { stop_func(); break; } else { std::cout << "输入字符:" << c << std::endl; } } FreeLibrary(hDll); return 0; }
注意事项
- 编译DLL时需定义
SCHEDULER_PLUGIN_EXPORTS宏,确保导出符号正确生成。 - 线程安全:任务函数涉及共享资源时,需在DLL或主程序中加锁保护。
- 资源清理:主程序退出前必须调用
StopScheduler,避免后台线程残留;释放DLL前确保所有DLL内线程已退出。 - 跨平台:Linux下可使用共享对象(.so),导出符号用
__attribute__((visibility("default"))),加载用dlopen、dlsym、dlclose。
内容的提问来源于stack exchange,提问作者Hrxun
相关产品推荐
相关产品推荐

