# [Legato] Mutex > https://docs.legato.io/latest/c_mutex.html https://docs.legato.io/latest/le__mutex_8h.html ## Recursive vs Non-Recursive Mutex Legato mutex 有分成 recursive 和 non-recursive 的 mutex。 - Recursive mutex - 同一個 thread 可以一直去鎖同一個 mutex,但鎖了幾次就要解鎖幾次。 - Non-recursive mutex - 一旦 mutex 已經被某個 thread 鎖住,而同一個 thread 若又嘗試將該 mutex 鎖住,會造成 deadlock,且 Legato 的機制一旦偵測到此情況,process 會 exit。 ## Legato Mutex 基本使用流程 1. 用以下兩個 API 建立 Legato mutex,,以下兩個 API 建立 mutex 後會 return `le_mutex_Ref_t`。 - [`le_mutex_CreateRecursive()`](https://docs.legato.io/latest/le__mutex_8h.html#ac7dd2b69f4b905d56df969c9085a570b):建立 recursive mutex。 - [`le_mutex_CreateNonRecursive()`](https://docs.legato.io/latest/le__mutex_8h.html#a602e2c18e646db7af0d68bb5fb103207):建立 non-recursive mutex。 - 不論是 recursive 或 non-recursive 的 mutex,後續的鎖住、解鎖、以及刪除的 API 都是用相同的,沒有分不同版本。 2. 鎖住 mutex 可用以下 API: - [`le_mutex_Lock()`](https://docs.legato.io/latest/le__mutex_8h.html#ad5b7d94710f420cd945229648e7a80e7) - 若 mutex 已被其他人鎖住,則呼叫此 API 的 thread 會被 block。 - [`le_mutex_TryLock()`](https://docs.legato.io/latest/le__mutex_8h.html#a43864999f70f0a825cf8ca87f9a2ee2c) - 此 API 會 return `le_result_t`,若 mutex 已被其他人鎖住則 return `LE_WOULD_BLOCK`;否則 return `LE_OK`。 3. 解鎖 mutex 則可用 [`le_mutex_Unlock()`](https://docs.legato.io/latest/le__mutex_8h.html#aae68b71222e20c55ff3bf2d7b52e3009)。 4. 由於 Legato mutex 是 dynamically allocated 物件,所以使用完 mutex 後記得呼叫 [`le_mutex_Delete()`](https://docs.legato.io/latest/le__mutex_8h.html#a38571fa1d9c15d5f30ea9c480d8810c6) 將其釋放。 ## Semaphore, Mutex, Spinlock PS. Legato 沒有 spinlock,這裡只是提出來一起做比較。 ### Ownership Semaphore 沒有 ownership 的概念,而 mutex 則有 ownership 概念。 假如某個 thread 取得了 mutex,將其鎖住了,則只有該 thread 可以解鎖,其他 thread 不行解鎖。++在 Legato 的機制,若試圖去解鎖一個已經被別人鎖住的 mutex,process 會 exit++。 Semaphore 由於沒有 ownership 的概念,所以其他 thread 可以執行 `le_sem_Post` 增加 semaphore 的值。 ### Blocking vs Non-Blocking Semaphore 和 mutex 都是屬於 blocking 機制,也就是若 mutex 已被鎖住或 semaphore 的值為 0,那之後來搶這個 semaphore/mutex 的人就會 sleep,會被放到 waiting list,這時就會把睡覺的人的 CPU 資源釋放出來,給其他人使用 CPU 資源,所以會有 context switch。 Spinlock 則是 non-blocking 機制,會一直 busy waiting 而不會釋放 CPU 資源,所以不會發生 context switch。另外,由於不會釋放 CPU 資源,因此 spinlock 適合用在 critical section 內執行時間極短的情況,因為若長時間 busy waiting 會浪費 CPU 資源。 ### Interrupt Service Routine ISR 不可被 block,所以 ISR 內不可使用 blocking 機制的 semaphore 和 mutex。 :::info Linux 的 Top-half 就是 ISR,所以 Linux 的 Top-half 不可使用 mutex 和 semaphore;但可使用 spinlock。 Bottom-half 則是用來處理可延後處理的事情,以減少 ISR 的執行時間。Linux 的機制有 workqueue、tasklet、以及 softirq。 Workqueue 執行在 process context,可以 sleep,所以 workqueue 內可使用 mutex 和 semaphore。 Tasklet 和 softirq 則執行在 interrupt context,不可 sleep,所以僅能用 spinlock。 ::: ## Example 1 以下例子是為了測試 mutex 的 ownership。 開兩個 worker thread,worker thread A 先去鎖 mutex,然後睡五秒,最後再解鎖 mutex。而 worker thread B 則會在 worker thread A 鎖住 mutex 時嘗試去將 mutex 解鎖。 ### Project Structure ``` ~/mutex/ ├── mutexApp.adef └── mutexComp ├── Component.cdef └── mutex.c ``` ### `mutexApp.adef` ```adef executables: { mutexExec = ( mutexComp ) } processes: { run: { mutexProc = ( mutexExec ) } } ``` ### `Component.cdef` ```cdef sources: { mutex.c } ``` ### `mutex.c` ```cpp #include "legato.h" #include "interfaces.h" #define THREAD_NUM (2) static le_thread_Ref_t workerThreadRef[THREAD_NUM] = {NULL}; static le_thread_DestructorRef_t workerThreadDestructorRef[THREAD_NUM] = {NULL}; static le_mutex_Ref_t mutex = NULL; static char name[20]; static char *destructorMsg = "inside worker thread destructor"; void workerThreadDestructor(void *context) { LE_INFO("[%s] %s", le_thread_GetMyName(), (char *) context); } void *workerThreadAFunc(void *context) { workerThreadDestructorRef[0] = le_thread_AddDestructor(workerThreadDestructor, (void *) destructorMsg); LE_INFO("[%s] is going to lock the mutex", le_thread_GetMyName()); le_mutex_Lock(mutex); LE_INFO("[%s] just locked the mutex", le_thread_GetMyName()); sleep(5); LE_INFO("[%s] is going to unlock the mutex", le_thread_GetMyName()); le_mutex_Unlock(mutex); LE_INFO("[%s] just unlocked the mutex", le_thread_GetMyName()); return NULL; } void *workerThreadBFunc(void *context) { workerThreadDestructorRef[1] = le_thread_AddDestructor(workerThreadDestructor, (void *) destructorMsg); sleep(1); // sleep for 1 second to make sure the workerThreadA lock the mutex LE_INFO("[%s] is going to try to unlock the mutex", le_thread_GetMyName()); le_mutex_Unlock(mutex); LE_INFO("[%s] just finished the unlock operation and is trying to lock the mutex", le_thread_GetMyName()); le_result_t res = le_mutex_TryLock(mutex); switch (res) { case LE_OK: LE_INFO("successfully lock the mutex"); break; case LE_WOULD_BLOCK: LE_INFO("mutex was already held by someone else"); break; default: LE_INFO("unknown error"); } return NULL; } void *(*workerThreadFuncs[]) (void *) = {workerThreadAFunc, workerThreadBFunc }; COMPONENT_INIT { mutex = le_mutex_CreateNonRecursive("MyMutex"); // creating threads and setting their thread functions for (int i = 0; i < THREAD_NUM; ++i) { snprintf(name, sizeof(name), "WorkerThread%c", 'A' + i); workerThreadRef[i] = le_thread_Create(name, workerThreadFuncs[i], NULL); le_thread_SetJoinable(workerThreadRef[i]); } // starting threads for (int i = 0; i < THREAD_NUM; ++i) { le_thread_Start(workerThreadRef[i]); } for (int i = 0; i < THREAD_NUM; ++i) { le_thread_Join(workerThreadRef[i], NULL); } le_mutex_Delete(mutex); } ``` ### Result ![image2024-9-19_9-17-38](https://hackmd.io/_uploads/B1vDu2SCR.png) 從以上截圖可以看到由於 mutex 已經被 worker thread A 持有,這時 mutex 的 owner 是 worker thread A,這時只有 worker thread A 可以將其解鎖。 所以當 worker thread B 嘗試將其解鎖時,就出現了 `Attempt to unlock mutex 'MyMutex' held by other thread 'WorkerThreadA'.` 的訊息,且 process 直接 exit。 ## Example 2 以下簡單的例子會建立一個 non-recursive mutex,看同一個 thread 嘗試對他鎖兩次會發生什麼事。 ```cpp #include "legato.h" #include "interfaces.h" #define THREAD_NUM (2) static le_thread_Ref_t workerThreadRef[THREAD_NUM] = {NULL}; static le_thread_DestructorRef_t workerThreadDestructorRef[THREAD_NUM] = {NULL}; static le_mutex_Ref_t mutex = NULL; static char name[20]; static char *destructorMsg = "inside worker thread destructor"; void workerThreadDestructor(void *context) { LE_INFO("[%s] %s", le_thread_GetMyName(), (char *) context); } void myFunction() { LE_INFO("[%s] inside myFunction and is going to lock the mutex", le_thread_GetMyName()); le_mutex_Lock(mutex); } void *workerThreadAFunc(void *context) { workerThreadDestructorRef[0] = le_thread_AddDestructor(workerThreadDestructor, (void *) destructorMsg); LE_INFO("[%s] is going to lock the mutex", le_thread_GetMyName()); le_mutex_Lock(mutex); LE_INFO("[%s] just locked the mutex", le_thread_GetMyName()); myFunction(); sleep(5); LE_INFO("[%s] is going to unlock the mutex", le_thread_GetMyName()); le_mutex_Unlock(mutex); LE_INFO("[%s] just unlocked the mutex", le_thread_GetMyName()); return NULL; } void *workerThreadBFunc(void *context) { workerThreadDestructorRef[1] = le_thread_AddDestructor(workerThreadDestructor, (void *) destructorMsg); sleep(1); // sleep for 1 second to make sure the workerThreadA lock the mutex LE_INFO("[%s] is going to unlock the mutex", le_thread_GetMyName()); le_mutex_Unlock(mutex); LE_INFO("[%s] just unlocked the mutex", le_thread_GetMyName()); return NULL; } void *(*workerThreadFuncs[]) (void *) = {workerThreadAFunc, workerThreadBFunc }; COMPONENT_INIT { mutex = le_mutex_CreateNonRecursive("MyMutex"); // creating threads and setting their thread functions for (int i = 0; i < THREAD_NUM; ++i) { snprintf(name, sizeof(name), "WorkerThread%c", 'A' + i); workerThreadRef[i] = le_thread_Create(name, workerThreadFuncs[i], NULL); le_thread_SetJoinable(workerThreadRef[i]); } // starting threads for (int i = 0; i < THREAD_NUM; ++i) { le_thread_Start(workerThreadRef[i]); } for (int i = 0; i < THREAD_NUM; ++i) { le_thread_Join(workerThreadRef[i], NULL); } le_mutex_Delete(mutex); } ``` ### Result ![image2024-9-19_10-48-56](https://hackmd.io/_uploads/S1l-t2rRC.png) 可以看到有偵測到 Deadlock,所以 process 就 exit 了。