So, if the only queue-writing task is running on Core 0 and the only queue-reading task is running on Core 1, must the queue be locked with a common mutex while each of these tasks is doing its thing?
Or, since only one task is writing and only one task is reading (even though on independently-running cores), there won't be a conflict and mutex-locking isn't required?
FreeRTOS queues are synchronized by the kernel, you don't need any mutexes in your own code.
See the example in the documentation: FreeRTOS - Open Source Software for Embedded Systems - FreeRTOS xQueueReceive() API function description and example
I couldn't find an explicit comment in the docs that you don't need mutexes, but this thread answers it, for example: Question: Safe to send structures to a queue from multiple tasks without mutex? - FreeRTOS
Note that having one task that reads and another that writes in general does require some form of synchronization, if not locks or mutexes then at least atomic variables.
In this case, the FreeRTOS kernel just handles it for you.
OK, thanks. That's pretty much what I figured. My only doubt was because the "vanilla FreeRTOS" documentation seems to assume a single-core environment while ESP32 has two cores. Good to know that the ESP32 port takes this into account with regard to queue management.
The ESP-IDF docs seem to use the same examples: FreeRTOS - ESP32 - — ESP-IDF Programming Guide latest documentation
It would indeed be a surprise if the Queue API would behave differently on ESP32, but I agree that it would be nice if the docs were more explicit about it.
Looking at the sources for the Queue send function, it looks like it does indeed use a mutex and enters a critical section: esp-idf/queue.c at 178b122c145c19e94ac896197a3a4a9d379cd618 · espressif/esp-idf · GitHub
Synchronization in a single-core system with preemptive scheduling isn't all that different from a multiprocessor environment, in both cases, you need mutexes/critical sections and atomic variables (although you don't need actual memory barriers on a single core, you still need to prevent the compiler from reordering your reads/writes to shared data). There are of course some differences, especially if you're writing the kernel/scheduler, but from user space it doesn't matter too much in most cases.
A final remark: given that the FreeRTOS Queue uses locks to allow any number of threads to access the queue at the same time, you might want to use a lock-free alternative if you have a single producer and a single consumer. This is important if performance of the queue is an issue, locks can slow things down significantly. You could Google for "SPSC queue" or "lock-free queue", for example. IIRC, Boost has one, and so does Folly.