Programming-related question: lets imagine we have an "atomic" variable and "atomic" functions (increment, decrement, compare). "Atomic" means that they (functions) can guarantee you consistency of the variable (we assume multitask environment).
What I want is to create a sync object, sort of mutex or semaphore which can be acquired multiple times by readers, but allow only one writer:
So different tasks can acquire READING lock at the same time without blocking. WRITING lock is more restrictive: if object is locked for writing, than any readers or writers will be blocked if they try to acquire lock.
The question:
How to implement that using basic atomic operations (++, --, OR, XOR and AND)
It's not clear what you mean by "lock" for either reading or writing. Neither operation requires locking for any appreciable amount of time ... just long enough to atomically copy the subject data to a local version or vice versa.
A multi-tasking environment presumes an operating system of some sort and context switching.
On a single-core processor, you can simply disable interrupts for just long enough to prevent a context switch while the code atomically copies the subject data to a local version or vice versa.
In a multi-core, multi-tasking configuration, the OS will most certainly have a feature to allow variable consistency across all cores and all tasks. For example, ESP32 with FreeRTOS has portENTER_CRITICAL.
Finally, consider the facilties provided by std::atomic.
I have lists. Single-linked lists, which are processed (read, traversing) by multiple tasks. Reader tasks. Rarely another task (Writer), goes through lists and apply some changes to them (i.e. add or remove entries).
So what I want is a mutex, which can be aquired multiple times for reading, or once exclusively, for writing.
If I just use a simple mutex or critical section, then one reader enters critical section and second reader will be blocked. Same for mutexes.
Here's the code from Post #10 modified to use a mutex instead of a critical section. The later would only be needed if access to the structures was needed by ISR code.
Nope. All requests for read access will succeed as long as no task has write access, meaning multiple tasks may have read access simultaneously. And, write access is only granted if no tasks have read or write access.
Another question is : lets imagine that we have 3 readers which obtain/release reader lock in a such way so there is no window in between. That would mean that writer task will never obtain writer lock. So it should be something: "block further reader lock requests if we have writer task blocking as well"
Complicated.
Currently I have implemented this rwlock_t object via one critical section, one counter and one biniary semaphore (FreeRTOS).
But it looks overcomplicated for such a simple thing as rwlock
True there would need to be some cooperation between the tasks. Especially, no task should hold on to its read (or write) access longer than it needs too.
I think your whole architecture is overcomplicated. Instead of giving all tasks potential access to the databases themselves, I'd have a single task that owns them. Read / write requests to the database from other tasks could be through a single queue to be serviced by the owning task. Results of reads could be pushed into individual queues (one per reading task) by the owner for the requesting task to pick up.
The writer task sets a flag that denies all reader tasks to obtain further locks (suspend). On the last reader unlock the writer can do its job and reset the flag and resume the suspended readers.
Again check for or implement something like xQueues.
As I mentioned in Post #12, Critical Sections are only required if ISR code needs to take part in acquiring/sharing the resource. Otherwise, a regular mutex will suffice, and likely be lighter weight.