Multithreading
Overview
This tutorial will show you how to work with Mongoose on a multithreaded environment.
As explicitly noted in the documentation, Mongoose is not thread-safe:
Since Mongoose's core is not protected against concurrent accesses, make sure that all mg_* API functions are called from the same thread or RTOS task.
To be able to use several threads, we need a mechanism that enables these threads to communicate with the main context in which the event manager is running. We can use a lock-free queue for that, though the event handler needs to poll for messages in that queue when it receives an MG_EV_POLL event. To have the event manager thread sleeping and wakeup when necessary, Mongoose provides a socket-based helper function.
The 'one to one' case
Overview
When serving static pages or even in dynamic RESTful servers, it is common to return content on the same callback function that handles the request. However, when that request triggers an action that requires a long processing time, we might want to take advantage of a multithreaded environment and spawn a new thread for each request to take care of it.
- The initialization sequence inits the wakeup scheme calling mg_wakeup_init()
- The client connects to our web server
- The usual event handler callback function receives the request, though this time, instead of handling it and sending a response, it will spawn a worker thread and pass the request to it
- The worker thread does its work and, when processing has finished, writes its response calling mg_wakeup(), frees its resources and exits
- This wakes up the event manager, and the web server event handler receives an
MG_EV_WAKEUP
event, carrying the response from the worker; then sends it to the client
Build and run
This example starts a web server that spawns a thread to handle each request. That thread will sleep for a second, simulating a long process, and send a response back. If you POST to it, it will send the CRC of the posted data, otherwise it will just let you know that.
- Follow the Build Tools tutorial to setup your development environment.
- Start a terminal in the project directory; if you've not already done so, clone the Mongoose Library repo
git clone https://github.com/cesanta/mongoose
- Build the example, this will also start Mongoose:
cd mongoose/tutorials/core/multi-threaded make clean all
- Go to
http://localhost:8000
in your browser; after two seconds, you'll see a response
How it works
For each incoming request:
- The event handler function receives an
MG_EV_HTTP_MSG
event- allocates memory for a structure
- duplicates the HTML message body containing the request
- spawns a new thread and hands over the request to it
- The worker thread:
- simulates a long processing time (parsing and executing the request)
- writes a response using mg_wakeup()
- frees resources and exits
- The event handler function receives an
MG_EV_WAKEUP
event- gets the response data and forwards it to the client
On program startup, we initialize the event manager and the wakeup scheme, create the listener, and call the event manager in an infinite loop:
The 'one to many' case
Overview
Embedded systems usually perform some measurement and or control task, and periodic reports of its status is commonly served to interested parties, usually over persistent WebSocket connections. For non-embedded applications, sometimes a single task distributes (streams) data (information, media) to multiple listeners. In these cases, there are no requests per se, but a task in continuous operation and a need to issue periodic updates. To simplify the development of such a task, we might want to take advantage of a multithreaded environment and spawn a thread to do that job, publishing to all existing connections.
The main difference with the former case, here, is that there is only one worker thread (or task thread), and it runs independently of the existence of any connections.
- The initialization sequence inits the wakeup scheme calling mg_wakeup_init()
- The event handler initialization sequence spawns a thread and passes manager and listener connection information to it.
- A client connects to our web server and its connection is upgraded to a WebSocket connection
- The task thread does its work and, when it is time for an update, writes the information by calling mg_wakeup()
- This wakes up the event manager, and the web server event handler receives an
MG_EV_WAKEUP
event, carrying data from the worker; it then scans all connections and sends that data to all registered clients.
Build and run
This example starts a web server that spawns a thread to perform a continuous job. That thread will sleep for some time, simulating some process, and send data to all connected WebSocket clients.
Follow the Build Tools tutorial to setup your development environment.
Start a terminal in the project directory; if you've not already done so, clone the Mongoose Library repo
git clone https://github.com/cesanta/mongoose
Build the example, this will also start Mongoose:
cd mongoose/tutorials/core/multi-threaded-12m make clean all
With a WebSocket client (e.g.: wscat), go to
ws://localhost:8000/websocket
; every two seconds, you'll see a greeting:wscat --connect ws://localhost:8000/websocket Connected (press CTRL+C to quit) < hi!
How it works
On program startup, we initialize the event manager and the wakeup scheme, create the listener, and call the event manager in an infinite loop, exactly as we did for the 'one to one' case:
The event handler function is initialized when we start the listener, it receives an MG_EV_OPEN
event. This also happens every time a new client connects and its connection is created, for that reason we check for the is_listening
flag, that will only be set on the listener. Then, we create the running task on starup after Mongoose is ready, as we pass a pointers to the manager and a listener identifier to it:
For each incoming request; the event handler function receives an MG_EV_HTTP_MSG
event; if it requests the /websocket
URI, we upgrade the connection and 'mark' it to identify it as a listener to whom we'll send data in due time.
The task thread is always running, it simulates some processing time and then writes data using mg_wakeup()
When this happens, the event handler function receives an MG_EV_WAKEUP
event; it then scans all connections searching for our 'mark' and replicates data to them