# [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 ![image](https://hackmd.io/_uploads/SyJo0qB00.png)