# [Legato] Semaphore
> https://docs.legato.io/latest/c_semaphore.html
https://docs.legato.io/latest/le__semaphore_8h.html
## Legato Semaphore 基本使用流程
1. 使用 [`le_sem_Create()`](https://docs.legato.io/latest/le__semaphore_8h.html#add9fab5440abcff5a8bc3b8bd1126d99) 建立 semaphore 並初始化 semaphore 的值,此 API 會 return `le_sem_Ref_t`。
- Semaphore 初始值必須大於等於 0,否則 process 會 exit。
2. 減少 semaphore 的值可以用以下 APIs:
- [`le_sem_Wait()`](https://docs.legato.io/latest/le__semaphore_8h.html#aecdf87fe330dd008771b7530edbb1f2b)
- [`le_sem_TryWait()`](https://docs.legato.io/latest/le__semaphore_8h.html#a6a6c435042dd37a3c78ebbab6ec72689)
- [`le_sem_WaitWithTimeOut()`](https://docs.legato.io/latest/le__semaphore_8h.html#a14475f0c2f5483427279d39220f55eaa)
3. 增加 semaphore 的值則可用 [`le_sem_Post()`](https://docs.legato.io/latest/le__semaphore_8h.html#abb859411cc58fbcc576c986ef52083b2)。
4. 取得目前 semaphore 的值可用 [`le_sem_GetValue()`](https://docs.legato.io/latest/le__semaphore_8h.html#ac4858ccb0ba748ca463bb29807b75c05)。
5. 由於 Legato semaphore 是 dynamically allocated 物件,所以當使用完 semaphore,記得呼叫 [`le_sem_Delete()`](https://docs.legato.io/latest/le__semaphore_8h.html#a96361b126f59934354ca17bf8b74b8f6) 將其釋放。
## 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
以下簡單的小例子示範用 semaphore 去做同步設定,讓 worker thread A、worker thread B、以及 worker thread C 依序印出 "This is WorkerThreadA"、"This is WorkerThreadB"、以及"This is WorkerThreadC"
### Project Structure
```
~/semaphore$ tree
.
├── semaphoreApp.adef
└── semaphoreComp
├── Component.cdef
└── semaphore.c
```
### `semaphoreApp.adef`
```adef
executables:
{
semaphoreExec = ( semaphoreComp )
}
processes:
{
run:
{
semaphoreProc = ( semaphoreExec )
}
}
```
### `Component.cdef`
```cdef
sources:
{
semaphore.c
}
```
### `semaphore.c`
```cpp
#include "legato.h"
#include "interfaces.h"
#define THREAD_NUM (3)
#define SEMAPHORE_NUM (2)
static le_thread_Ref_t workerThreadRef[THREAD_NUM] = {NULL};
static le_thread_DestructorRef_t workerThreadDestructorRef[THREAD_NUM] = {NULL};
static le_sem_Ref_t semaphoreRef[SEMAPHORE_NUM] = {NULL};
static char name[20];
static char *destructorMsg = "inside worker thread destructor";
void threadDestructor(void *context)
{
LE_INFO("[%s] %s", le_thread_GetMyName(), (char *) context);
}
void *workerThreadAFunc(void *context)
{
workerThreadDestructorRef[0] = le_thread_AddDestructor(threadDestructor, (void *) destructorMsg);
LE_INFO("This is %s", le_thread_GetMyName());
le_sem_Post(semaphoreRef[0]);
return NULL;
}
void *workerThreadBFunc(void *context)
{
workerThreadDestructorRef[1] = le_thread_AddDestructor(threadDestructor, (void *) destructorMsg);
le_sem_Wait(semaphoreRef[0]);
LE_INFO("This is %s", le_thread_GetMyName());
le_sem_Post(semaphoreRef[1]);
return NULL;
}
void *workerThreadCFunc(void *context)
{
workerThreadDestructorRef[2] = le_thread_AddDestructor(threadDestructor, (void *) destructorMsg);
le_sem_Wait(semaphoreRef[1]);
LE_INFO("This is %s", le_thread_GetMyName());
return NULL;
}
void *( *workerThreadFuncs[] ) (void *) = { workerThreadAFunc, workerThreadBFunc, workerThreadCFunc };
COMPONENT_INIT
{
// create semaphores and initialize their values to 0
for (int i = 0; i < SEMAPHORE_NUM; ++i)
{
snprintf(name, sizeof(name), "Semaphore%c", 'A' + i);
semaphoreRef[i] = le_sem_Create(name, 0);
}
// create threads and set 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]);
}
// start threads
for (int i = THREAD_NUM - 1; i >= 0; --i)
{
le_thread_Start(workerThreadRef[i]);
}
for (int i = 0; i < THREAD_NUM; ++i)
{
le_thread_Join(workerThreadRef[i], NULL);
}
// delete semaphores
LE_INFO("[%s] ready to delete semaphores", le_thread_GetMyName());
for (int i = 0; i < SEMAPHORE_NUM; ++i)
{
le_sem_Delete(semaphoreRef[i]);
}
LE_INFO("[%s] all semaphores deleted", le_thread_GetMyName());
}
```
### Result
