Using mutex for mutually exclusive tasks

I have a mutex semaphore created with xSemaphoreCreateMutex

I call task1:xSemaphoreGive, and immediately next task1:xSemaphoreTake from the same task.
Is it guaranteed that, if there is another task taskX waiting with taskX:xSemaphoreTake for this semaphore, it will take this semaphore and taskX will not be skipped?

It there a way to guarantee this?

what priorities do the tasks have?

IDK if priority matters?
Both tasks are created using xTaskCreate(), with random priorities, and maybo on different CPUs

Priorities do make a lot of difference. If giving a mutex makes a higher priority task ready to run, a switch to that task will occur. If a waitingbtask has a lower priority than the task giving the mutex, there will be no task switch. If both have equal priority, I do not think that the standard scheduling policy will switch to the task now ready, but there may be policies that preempt the giver.

With muteces, there may be skews due to priority inheritance, but that is irrelevant for this discussion (what counts is the priority at the moment of giving the mutex).

A key point about the mattering of priorities is that just because you give a semaphore that another task is waiting on, that task doesn’t “take” the semaphore until it is run.

Thus, if the waiting task has a lower priority than the current task, the semaphore will still be available when the task tries to retake the semaphore.

If the tasks are the same priority, the give will not force a context switch, but a timer interrupt between the give and the take can, or you can put an explicit yield to make it happen.

1 Like

Thanks. So if taskX is lower priority than task, it will not get control, even if I place taskYIELD between those commands. Thanks.

I’m thinking to make this using task notify mechanism maybe.

well, you will not be able to undermine task priorities. Higher prioritized tasks will by definition always run preferred over low pri tasks, that is what an rtos is all about.

1 Like

One thing to point out is that the pattern that seems to be described here, a task A that runs to a point, and then “triggers” a task B to run, and A waits for it to finish, and that is the only case where task B is run, is generally an “anti-pattern”, and could more simply be done by having the code for task B just inserted at that point in task (perhaps being a function call).

The task concept is really about sections of code that at least potentially act like they might be running concurrently, so the strict alternation of the operation is sign that they are not really seperate “tasks” in this sense, but just different steps within one bigger task.

There ARE cases where it makes sense to do it this way, but they are fairly rare.

In my case, taskX “binds” using mutex itself as a receiver from task1, where task1 is uart communication task with AT GSM chip.
then taskX sends uart data, reads response and unbinds.

on incoming uart data, task1 transmits this data to taskX if there is some taskX waiting for data, checks if data was absorbed by taskX or not, and if the data was not absorbed or no taskX is bound, it sends data to external function process_unsolicited

This is made for using form like (pseudocode) getGsmResponse(modem*, cmd="AT+SOME?", ..., (va args) expected responses = "OK", ERROR") from any place of code.

well, we had this issue before: Trying to communicate with the same UART from multiple tasks inherently is a bad design. Richard’s suspicion was spot on: The communication is inherently sequential and single threaded, but your design asks for muliple threads which then must be re-sequentialized through the back door. That should tip you off that something is wrong with your design. Do NOT attempt to share a sequential half duplex comm device between multiple tasks.

Well, i don’t see any way i can get it any other right way.

I have several tasks that need to “signal” uart to give some kind of information like “get vendor info” or “get signal strength”, and uart itself can be source of interrupt for whole system, like “incoming call”.

Because of “RING (incoming call)” can happen, I have to make a uart task that monitors uart line (in my case, I have no RI line). Same time, i have few tasks that can query for some data like “send sms” or “get signal strength” or “get vendor information”.

And “RING” can happen in between of request-response data.

I don’t see any way I can get it except “binding” to output of uart loop task.

You should have only one task writing to the uart. That task is typically i plemented as a message pump that may be fed from other tasks with messages that i struct the writer what to emit. After writing the task typical,y reads the response and then dispatches accordingly, possibly keeping a state machine that keeps track of the connection state.

If you attempt to try to write to the device from several tasks, the result is exactly like Richard predicted: You must lock your tasks so tightly to each other that they effectively act as coroutines of one another.

Edit. You may study ppp drivers of network stacks such as lwip. Those address very similar issues and generally xerve as reasonable blue prints.

Writing to UART from different tasks, but using single function and mutex is a bad idea, or it is acceptable?

It does not make sense because the sequence of reads/writes does not benefit from multithreading; the protocol is inherently sequential, so you must funnel all your concurreny back into single-threadedness. So you do not only lose all mulitasking benefits but risk all of its potential dangers plus a significant added footprint.

To me that is unacceptable because there are no pros to doing it that way, only cons.

I do that a lot (for log output), but you need mutex (or similar) locking to pass “ownership” of the UART from task to task to avoid garbling the messages.

If it is an external serial device that you talk to, normally it makes more sense for a single task to process (and combine related requests) but I have done it with a mutex sharing if two (or more) tasks are accessing different parts of a device.

I expected my code will be something like that.
So I can start gsmdevice_task task and call gmsdevice_query from anywhere I want, so gsmdevice_task and task calling gmsdevice_query will work in very linked way through this gmsdevice_query

As I see, there is a problem with lines marked // ??? about mutually exclusive switching between tasks.

void gsmdevice_task(void *pvParams) {
    struct gsmdevice_data* gd_data = ((struct gsmdevice_data*)pvParams);
    TaskHandle_t query_task = NULL;
    for(;;) {
        // this is for simplification, assume this func returns correct c-str
        if (uart_read_line(gd_data->rx_buffer)) {
             // If there is a task performing query
            if ((query_task = xSemaphoreGetMutexHolder(gd_data->mtx_binding)) != NULL) {
                gd_data->rx_buffer_processed = false;
                // ??? Give control to task `query_task` holding gd_data->mtx_binding 
                // ??? Wait for query_task to give control back
                if (!gd_data->rx_buffer_processed)
                    printf("UNHANDLED: %s", gd_data->rx_buffer);
                }
            else
                printf("UNHANDLED: %s", gd_data->rx_buffer);
        }
    }
}


void gmsdevice_query(struct gsmdevice_data* gd_data, const char* query, const char* resp1, const char* resp2) {
    if(pdTRUE != xSemaphoreTake(gd_data->mtx_binding, portMAX_DELAY)) return; // bind or return
    uart_write_line(query); // simplification

    bool finished = false;
    do {
        // ??? Wait until gsmdevice_process_rx_line gives us control
        if (strcmp(gd_data->rx_buffer, resp1) == 0 || strcmp(gd_data->rx_buffer, resp2) == 0) {
            printf("QUERY END: %s", gd_data->rx_buffer);
            gd_data->rx_buffer_processed = true;
            xSemaphoreGive(gd_data->mtx_binding); // unbind
            finished = true;
        }
        else if (strncmp(gd_data->rx_buffer, "+CEE", 4) == 0) {
            printf("QUERY DATA: %s", gd_data->rx_buffer);
            gd_data->rx_buffer_processed = true; }
        else {
            printf("UNHANDLED BY QUERY: %s", gd_data->rx_buffer);
            gd_data->rx_buffer_processed = false; }

        // ??? return control to gsmdevice_process_rx_line
    } while (!finished);
}

If the serial port has incoming asynchronous requests/actions that need to be handled, the correct solution is normally to have a dedicated task to handle the communication, so it is always looking for those operations, and THAT task also takes requests from other tasks that it relays.

My experience is that normally every source of asynchronous requests tends to need its own task. (some similar ones might be able to share)