{"id":21274161,"url":"https://github.com/endurodave/stdworkerthread","last_synced_at":"2025-09-15T00:15:14.047Z","repository":{"id":44376466,"uuid":"96714513","full_name":"endurodave/StdWorkerThread","owner":"endurodave","description":"C++ std::thread Event Loop with Message Queue and Timer","archived":false,"fork":false,"pushed_at":"2025-06-22T15:08:56.000Z","size":43,"stargazers_count":42,"open_issues_count":0,"forks_count":17,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-11T08:48:41.721Z","etag":null,"topics":["cpp","cross-platform","event-loop","linux","multi-threading","std-thread","stl","windows"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/endurodave.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-07-09T22:14:45.000Z","updated_at":"2025-07-07T15:48:48.000Z","dependencies_parsed_at":"2025-03-11T13:25:36.667Z","dependency_job_id":"10ee5a89-b9de-416c-81fe-5708ea18f21b","html_url":"https://github.com/endurodave/StdWorkerThread","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/endurodave/StdWorkerThread","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStdWorkerThread","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStdWorkerThread/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStdWorkerThread/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStdWorkerThread/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endurodave","download_url":"https://codeload.github.com/endurodave/StdWorkerThread/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStdWorkerThread/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275185711,"owners_count":25419979,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-14T02:00:10.474Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cross-platform","event-loop","linux","multi-threading","std-thread","stl","windows"],"created_at":"2024-11-21T09:19:11.685Z","updated_at":"2025-09-15T00:15:13.956Z","avatar_url":"https://github.com/endurodave.png","language":"C++","readme":"![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue)\n[![conan Ubuntu](https://github.com/endurodave/StdWorkerThread/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/StdWorkerThread/actions/workflows/cmake_ubuntu.yml)\n[![conan Ubuntu](https://github.com/endurodave/StdWorkerThread/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/StdWorkerThread/actions/workflows/cmake_clang.yml)\n[![conan Windows](https://github.com/endurodave/StdWorkerThread/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/StdWorkerThread/actions/workflows/cmake_windows.yml)\n\n# C++ std::thread Event Loop with Message Queue and Timer\n\nCreate a worker thread with an event loop, message queue and a timer using the C++11 thread support library.\n\n# Table of Contents\n\n- [C++ std::thread Event Loop with Message Queue and Timer](#c-stdthread-event-loop-with-message-queue-and-timer)\n- [Table of Contents](#table-of-contents)\n- [Preface](#preface)\n- [Getting Started](#getting-started)\n- [Introduction](#introduction)\n- [Background](#background)\n- [WorkerThread](#workerthread)\n- [Event Loop](#event-loop)\n  - [Event Loop (Win32)](#event-loop-win32)\n- [Timer](#timer)\n- [Usage](#usage)\n- [Related Repositories](#related-repositories)\n- [Conclusion](#conclusion)\n\n# Preface\n\nOriginally published on CodeProject at: \u003ca href=\"http://www.codeproject.com/Articles/1169105/Cplusplus-std-thread-Event-Loop-with-Message-Queue\"\u003e\u003cstrong\u003eC++ std::thread Event Loop with Message Queue and Timer\u003c/strong\u003e\u003c/a\u003e\n\n# Getting Started\n\n[CMake](https://cmake.org/) is used to create the project build files on any Windows or Linux machine. The source code works on any C++ compiler with `std::thread` support.\n\n1. Clone the repository.\n2. From the repository root, run the following CMake command:   \n   `cmake -B Build .`\n3. Build and run the project within the `Build` directory. \n\n# Introduction\n\n\u003cp\u003eAn event loop, or sometimes called a message loop, is a thread that waits for and dispatches incoming events. The thread blocks waiting for requests to arrive and then dispatches the event to an event handler function. A message queue is typically used by the loop to hold incoming messages. Each message is sequentially dequeued, decoded, and then an action is performed. Event loops are one way to implement inter-process communication.\u003c/p\u003e\n\n\u003cp\u003eAll operating systems provide support for multi-threaded applications. Each OS has unique function calls for creating threads, message queues and timers. With the advent of the C++11 thread support library, it\u0026rsquo;s now possible to create portable code and avoid the OS-specific function calls. This article provides a simple example of how to create a thread event loop, message queue and timer services while only relying upon the C++ Standard Library. Any C++11 compiler supporting the thread library should be able to compile the attached source.\u003c/p\u003e\n\n# Background\n\n\u003cp\u003eTypically, I need a thread to operate as an event loop. Incoming messages are dequeued by the thread and data is dispatched to an appropriate function handler based on a unique message identifier. Timer support capable of invoking a function is handy for low speed polling or to generate a timeout if something doesn\u0026rsquo;t happen in the expected amount of time. Many times, the worker thread is created at startup and isn\u0026rsquo;t destroyed until the application terminates.\u003c/p\u003e\n\n\u003cp\u003eA key requirement for the implementation is that the incoming messages must execute on the same thread instance. Whereas say \u003ccode\u003estd::async \u003c/code\u003emay use a temporary thread from a pool, this class ensures that all incoming messages use the same thread. For instance, a subsystem could be implemented with code that is not thread-safe. A single \u003ccode\u003eWorkerThread \u003c/code\u003einstance is used to safely dispatch function calls into the subsystem.\u003c/p\u003e\n\n\u003cp\u003eAt first glance, the C++ thread support seems to be missing some key features. Yes, \u003ccode\u003estd::thread \u003c/code\u003eis available to spin off a thread but there is no thread-safe queue and no timers \u0026ndash; services that most OS\u0026rsquo;s provide. I\u0026rsquo;ll show how to use the C++ Standard Library to create these \u0026ldquo;missing\u0026rdquo; features and provide an event processing loop familiar to many programmers.\u003c/p\u003e\n\n# WorkerThread\n\n\u003cp\u003eThe \u003ccode\u003eWorkerThread \u003c/code\u003eclass encapsulates all the necessary event loop mechanisms. A simple class interface allows thread creation, posting messages to the event loop, and eventual thread termination. The interface is shown below:\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nclass WorkerThread\n{\npublic:\n\u0026nbsp; \u0026nbsp; /// Constructor\n\u0026nbsp; \u0026nbsp; WorkerThread(const char* threadName);\n\n\u0026nbsp; \u0026nbsp; /// Destructor\n\u0026nbsp; \u0026nbsp; ~WorkerThread();\n\n\u0026nbsp; \u0026nbsp; /// Called once to create the worker thread\n\u0026nbsp; \u0026nbsp; /// @return True if thread is created. False otherwise.\u0026nbsp;\n\u0026nbsp; \u0026nbsp; bool CreateThread();\n\n\u0026nbsp; \u0026nbsp; /// Called once a program exit to exit the worker thread\n\u0026nbsp; \u0026nbsp; void ExitThread();\n\n\u0026nbsp; \u0026nbsp; /// Get the ID of this thread instance\n\u0026nbsp; \u0026nbsp; /// @return The worker thread ID\n\u0026nbsp; \u0026nbsp; std::thread::id GetThreadId();\n\n\u0026nbsp; \u0026nbsp; /// Get the ID of the currently executing thread\n\u0026nbsp; \u0026nbsp; /// @return The current thread ID\n\u0026nbsp; \u0026nbsp; static std::thread::id GetCurrentThreadId();\n\n\u0026nbsp; \u0026nbsp; /// Add a message to the thread queue\n\u0026nbsp; \u0026nbsp; /// @param[in] data - thread specific message information\n\u0026nbsp; \u0026nbsp; void PostMsg(std::shared_ptr\u0026lt;UserData\u0026gt; msg);\n\nprivate:\n\u0026nbsp; \u0026nbsp; WorkerThread(const WorkerThread\u0026amp;) = delete;\n\u0026nbsp; \u0026nbsp; WorkerThread\u0026amp; operator=(const WorkerThread\u0026amp;) = delete;\n\n\u0026nbsp; \u0026nbsp; /// Entry point for the worker thread\n\u0026nbsp; \u0026nbsp; void Process();\n\n\u0026nbsp; \u0026nbsp; /// Entry point for timer thread\n\u0026nbsp; \u0026nbsp; void TimerThread();\n\n\u0026nbsp; \u0026nbsp; std::unique_ptr\u0026lt;std::thread\u0026gt; m_thread;\n\u0026nbsp; \u0026nbsp; std::queue\u0026lt;std::shared_ptr\u0026lt;ThreadMsg\u0026gt;\u0026gt; m_queue;\n\u0026nbsp; \u0026nbsp; std::mutex m_mutex;\n\u0026nbsp; \u0026nbsp; std::condition_variable m_cv;\n\u0026nbsp; \u0026nbsp; std::atomic\u0026lt;bool\u0026gt; m_timerExit;\n\u0026nbsp; \u0026nbsp; const char* THREAD_NAME;\n};\u003c/pre\u003e\n\n\u003cp\u003eThe first thing to notice is that \u003ccode\u003estd::thread \u003c/code\u003eis used to create a main worker thread. The main worker thread function is \u003ccode\u003eProcess()\u003c/code\u003e.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nbool WorkerThread::CreateThread()\n{\n    if (!m_thread)\n        m_thread = new thread(\u0026amp;WorkerThread::Process, this);\n    return true;\n}\u003c/pre\u003e\n\n# Event Loop\n\n\u003cp\u003eThe \u003ccode\u003eProcess() \u003c/code\u003eevent loop is shown below. The thread relies upon a \u003ccode\u003estd::queue\u0026lt;ThreadMsg*\u0026gt; \u003c/code\u003efor the message queue. \u003ccode\u003estd::queue \u003c/code\u003eis not thread-safe so all access to the queue must be protected by mutex. A \u003ccode\u003estd::condition_variable \u003c/code\u003eis used to suspend the thread until notified that a new message has been added to the queue.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nvoid WorkerThread::Process()\n{\n\u0026nbsp; \u0026nbsp; m_timerExit = false;\n\u0026nbsp; \u0026nbsp; std::thread timerThread(\u0026amp;WorkerThread::TimerThread, this);\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;while (1)\n\u0026nbsp;\u0026nbsp; \u0026nbsp;{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;std::shared_ptr\u0026lt;ThreadMsg\u0026gt; msg;\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;// Wait for a message to be added to the queue\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;std::unique_lock\u0026lt;std::mutex\u0026gt; lk(m_mutex);\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;while (m_queue.empty())\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;m_cv.wait(lk);\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;if (m_queue.empty())\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;continue;\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;msg = m_queue.front();\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;m_queue.pop();\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;}\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;switch (msg-\u0026gt;id)\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;case MSG_POST_USER_DATA:\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;ASSERT_TRUE(msg-\u0026gt;msg != NULL);\n\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; auto userData = std::static_pointer_cast\u0026lt;UserData\u0026gt;(msg-\u0026gt;msg);\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; cout \u0026lt;\u0026lt; userData-\u0026gt;msg.c_str() \u0026lt;\u0026lt; \u0026quot; \u0026quot; \u0026lt;\u0026lt; userData-\u0026gt;year \u0026lt;\u0026lt; \u0026quot; on \u0026quot; \u0026lt;\u0026lt; THREAD_NAME \u0026lt;\u0026lt; endl;\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;break;\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;}\n\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; case MSG_TIMER:\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; cout \u0026lt;\u0026lt; \u0026quot;Timer expired on \u0026quot; \u0026lt;\u0026lt; THREAD_NAME \u0026lt;\u0026lt; endl;\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; break;\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;case MSG_EXIT_THREAD:\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;{\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; m_timerExit = true;\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; timerThread.join();\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; return;\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;}\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;default:\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;ASSERT();\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;}\n\u0026nbsp;\u0026nbsp; \u0026nbsp;}\n}\u003c/pre\u003e\n\n\u003cp\u003e\u003ccode\u003ePostMsg() \u003c/code\u003ecreates a new \u003ccode\u003eThreadMsg \u003c/code\u003eon the heap, adds the message to the queue, and then notifies the worker thread using a condition variable.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nvoid WorkerThread::PostMsg(std::shared_ptr\u0026lt;UserData\u0026gt; data)\n{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;ASSERT_TRUE(m_thread);\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Create a new ThreadMsg\n\u0026nbsp; \u0026nbsp; std::shared_ptr\u0026lt;ThreadMsg\u0026gt; threadMsg(new ThreadMsg(MSG_POST_USER_DATA, data));\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Add user data msg to queue and notify worker thread\n\u0026nbsp;\u0026nbsp; \u0026nbsp;std::unique_lock\u0026lt;std::mutex\u0026gt; lk(m_mutex);\n\u0026nbsp;\u0026nbsp; \u0026nbsp;m_queue.push(threadMsg);\n\u0026nbsp;\u0026nbsp; \u0026nbsp;m_cv.notify_one();\n}\u003c/pre\u003e\n\n\u003cp\u003eThe loop will continue to process messages until the \u003ccode\u003eMSG_EXIT_THREAD \u003c/code\u003eis received and the thread exits.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nvoid WorkerThread::ExitThread()\n{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;if (!m_thread)\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;return;\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Create a new ThreadMsg\n\u0026nbsp;\u0026nbsp; \u0026nbsp;std::shared_ptr\u0026lt;ThreadMsg\u0026gt; threadMsg(new ThreadMsg(MSG_EXIT_THREAD, 0));\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Put exit thread message into the queue\n\u0026nbsp;\u0026nbsp; \u0026nbsp;{\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;lock_guard\u0026lt;mutex\u0026gt; lock(m_mutex);\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;m_queue.push(threadMsg);\n\u0026nbsp;\u0026nbsp; \u0026nbsp;\u0026nbsp;\u0026nbsp; \u0026nbsp;m_cv.notify_one();\n\u0026nbsp;\u0026nbsp; \u0026nbsp;}\n\n\u0026nbsp; \u0026nbsp; m_thread-\u0026gt;join();\n\u0026nbsp; \u0026nbsp; m_thread = nullptr;\n}\u003c/pre\u003e\n\n## Event Loop (Win32)\n\n\u003cp\u003eThe code snippet below contrasts the \u003ccode\u003estd::thread \u003c/code\u003eevent loop above with a similar Win32 version using the Windows API. Notice \u003ccode\u003eGetMessage() \u003c/code\u003eAPI is used in lieu of the \u003ccode\u003estd::queue\u003c/code\u003e. Messages are posted to the OS message queue using \u003ccode\u003ePostThreadMessage()\u003c/code\u003e. And finally, \u003ccode\u003etimerSetEvent() \u003c/code\u003eis used to place \u003ccode\u003eWM_USER_TIMER \u003c/code\u003emessages into the queue. All of these services are provided by the OS. The \u003ccode\u003estd::thread WorkerThread \u003c/code\u003eimplementation presented here avoids the raw OS calls yet the implementation functionality is the same as the Win32 version while relying only upon only the C++ Standard Library.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nunsigned long WorkerThread::Process(void* parameter)\n{\n    MSG msg;\n    BOOL bRet;\n\n    // Start periodic timer\n    MMRESULT timerId = timeSetEvent(250, 10, \u0026amp;WorkerThread::TimerExpired, \n                       reinterpret_cast\u0026lt;DWORD\u0026gt;(this), TIME_PERIODIC);\n\n    while ((bRet = GetMessage(\u0026amp;msg, NULL, WM_USER_BEGIN, WM_USER_END)) != 0)\n    {\n        switch (msg.message)\n        {\n            case WM_DISPATCH_DELEGATE:\n            {\n                ASSERT_TRUE(msg.wParam != NULL);\n\n                // Convert the ThreadMsg void* data back to a UserData*\n                const UserData* userData = static_cast\u0026lt;const UserData*\u0026gt;(msg.wParam);\n\n                cout \u0026lt;\u0026lt; userData-\u0026gt;msg.c_str() \u0026lt;\u0026lt; \u0026quot; \u0026quot; \u0026lt;\u0026lt; userData-\u0026gt;year \u0026lt;\u0026lt; \u0026quot; on \u0026quot; \u0026lt;\u0026lt; THREAD_NAME \u0026lt;\u0026lt; endl;\n\n                // Delete dynamic data passed through message queue\n                delete userData;\n                break;\n            }\n\n            case WM_USER_TIMER:\n                cout \u0026lt;\u0026lt; \u0026quot;Timer expired on \u0026quot; \u0026lt;\u0026lt; THREAD_NAME \u0026lt;\u0026lt; endl;\n                break;\n\n            case WM_EXIT_THREAD:\n                timeKillEvent(timerId);\n                return 0;\n\n            default:\n                ASSERT();\n        }\n    }\n    return 0;\n}\u003c/pre\u003e\n\n# Timer\n\n\u003cp\u003eA low-resolution periodic timer message is inserted into the queue using a secondary private thread. The timer thread is created inside \u003ccode\u003eProcess()\u003c/code\u003e.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nvoid WorkerThread::Process()\n{\n    m_timerExit = false;\n    std::thread timerThread(\u0026amp;WorkerThread::TimerThread, this);\n\n...\u003c/pre\u003e\n\n\u003cp\u003eThe timer thread\u0026rsquo;s sole responsibility is to insert a \u003ccode\u003eMSG_TIMER \u003c/code\u003emessage every 250ms. In this implementation, there\u0026rsquo;s no protection against the timer thread injecting more than one timer message into the queue. This could happen if the worker thread falls behind and can\u0026rsquo;t service the message queue fast enough. Depending on the worker thread, processing load, and how fast the timer messages are inserted, additional logic could be employed to prevent flooding the queue.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\nvoid WorkerThread::TimerThread()\n{\n\u0026nbsp; \u0026nbsp; while (!m_timerExit)\n\u0026nbsp; \u0026nbsp; {\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // Sleep for 250mS then put a MSG_TIMER into the message queue\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; std::this_thread::sleep_for(250ms);\n\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; std::shared_ptr\u0026lt;ThreadMsg\u0026gt; threadMsg (new ThreadMsg(MSG_TIMER, 0));\n\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // Add timer msg to queue and notify worker thread\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; std::unique_lock\u0026lt;std::mutex\u0026gt; lk(m_mutex);\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; m_queue.push(threadMsg);\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; m_cv.notify_one();\n\u0026nbsp; \u0026nbsp; }\n}\u003c/pre\u003e\n\n# Usage\n\n\u003cp\u003eThe \u003ccode\u003emain()\u003c/code\u003e function below shows how to use the \u003ccode\u003eWorkerThread \u003c/code\u003eclass. Two worker threads are created and a message is posted to each one. After a short delay, both threads exit.\u003c/p\u003e\n\n\u003cpre lang=\"C++\"\u003e\n// Worker thread instances\nWorkerThread workerThread1(\u0026quot;WorkerThread1\u0026quot;);\nWorkerThread workerThread2(\u0026quot;WorkerThread2\u0026quot;);\n\nint main(void)\n{\u0026nbsp;\u0026nbsp; \u0026nbsp;\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Create worker threads\n\u0026nbsp;\u0026nbsp; \u0026nbsp;workerThread1.CreateThread();\n\u0026nbsp;\u0026nbsp; \u0026nbsp;workerThread2.CreateThread();\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Create message to send to worker thread 1\n\u0026nbsp;\u0026nbsp; \u0026nbsp;std::shared_ptr\u0026lt;UserData\u0026gt; userData1(new UserData());\n\u0026nbsp;\u0026nbsp; \u0026nbsp;userData1-\u0026gt;msg = \u0026quot;Hello world\u0026quot;;\n\u0026nbsp;\u0026nbsp; \u0026nbsp;userData1-\u0026gt;year = 2017;\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Post the message to worker thread 1\n\u0026nbsp;\u0026nbsp; \u0026nbsp;workerThread1.PostMsg(userData1);\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Create message to send to worker thread 2\n\u0026nbsp;\u0026nbsp; \u0026nbsp;std::shared_ptr\u0026lt;UserData\u0026gt; userData2(new UserData());\n\u0026nbsp;\u0026nbsp; \u0026nbsp;userData2-\u0026gt;msg = \u0026quot;Goodbye world\u0026quot;;\n\u0026nbsp;\u0026nbsp; \u0026nbsp;userData2-\u0026gt;year = 2017;\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Post the message to worker thread 2\n\u0026nbsp;\u0026nbsp; \u0026nbsp;workerThread2.PostMsg(userData2);\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;// Give time for messages processing on worker threads\n\u0026nbsp;\u0026nbsp; \u0026nbsp;this_thread::sleep_for(1s);\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;workerThread1.ExitThread();\n\u0026nbsp;\u0026nbsp; \u0026nbsp;workerThread2.ExitThread();\n\n\u0026nbsp;\u0026nbsp; \u0026nbsp;return 0;\n}\u003c/pre\u003e\n\n# Related Repositories\n\nReal-world projects using `WorkerThread`.\n\n* \u003ca href=\"https://github.com/endurodave/DelegateMQ\"\u003eAsynchronous Delegates in C++\u003c/a\u003e - Invoke any C++ callable function synchronously, asynchronously, or on a remote endpoint.\n* \u003ca href=\"https://github.com/endurodave/StateMachineWithThreads\"\u003eC++ State Machine with Threads\u003c/a\u003e - A framework combining C++ state machines and multicast asynchronous callbacks.\n* \u003ca href=\"https://github.com/endurodave/StateMachineWithModernDelegates\"\u003eC++ State Machine with Asynchronous Delegates\u003c/a\u003e - A framework combining C++ state machines and asynchronous delegate callbacks.\n* \u003ca href=\"https://github.com/endurodave/AsyncStateMachine\"\u003eAsynchronous State Machine Design in C++\u003c/a\u003e - An asynchronous C++ state machine implemented using an asynchronous delegate library.\n* \u003ca href=\"https://github.com/endurodave/IntegrationTestFramework\"\u003eIntegration Test Framework using Google Test and Delegates\u003c/a\u003e - A multi-threaded C++ software integration test framework using Google Test and DelegateMQ libraries.\n* \u003ca href=\"https://github.com/endurodave/Async-SQLite\"\u003eAsynchronous SQLite API using C++ Delegates\u003c/a\u003e - An asynchronous SQLite wrapper implemented using an asynchronous delegate library.\n\n# Conclusion\n\n\u003cp\u003eThe C++ thread support library offers a platform independent way to write multi-threaded application code without reliance upon OS-specific API\u0026rsquo;s. The \u003ccode\u003eWorkerThread \u003c/code\u003eclass presented here is a bare-bones implementation of an event loop, yet all the basics are there ready to be expanded upon.\u003c/p\u003e\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fstdworkerthread","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendurodave%2Fstdworkerthread","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fstdworkerthread/lists"}