# [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

從以上截圖可以看到由於 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

可以看到有偵測到 Deadlock,所以 process 就 exit 了。