This tutorial will show you how to work with Mongoose on a multithreaded environment.
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.
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'll use a lock-free queue for that.
- 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 allocates a queue and, when processing has finished, writes its response to it
- The web server polls the queue, when it sees the response from the worker, sends it to the client
- The worker thread polls the queue, when it sees the server has read the response, frees its resources and exits
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/examples/multi-threaded make clean all
- Either go to
http://localhost:8000in your browser; after a second, you'll see an instruction text
- Or POST something; after a second, you'll see the calculated CRC
curl -X POST http://localhost:8000/ -d 1234 crc32: 0x9be3e0a3
How it works
For each incoming request:
- The event handler function
- allocates memory
- duplicates the HTML message body containing the request
- spawns a new thread and hands over the request to it We store a pointer to the data structure we use to communicate with the worker, in order to reference it later
- The worker thread:
- allocates memory in the stack for the queue
- initializes a queue by calling mg_queue_init()
- simulates a long processing time (parsing and executing the request)
- writes a response to the queue using mg_queue_printf()
- Remains polling the queue to detect when the request handler has read the response
On every poll interval, that is, every time the configured poll time elapses or faster:
- The event handler function polls the queue to check for an outstanding message from the worker by calling mg_queue_next(). If there is one, it fowards it to the client and deletes it from the queue by calling mg_queue_del()
The worker thread will see when the message has been deleted from the queue (the queue is now empty), free the memory formerly allocated by the event handler function and exit:
On program startup, we initialize the event manager, create the listener, and call the event manager in an infinite loop: