owned this note
owned this note
Published
Linked with GitHub
# [AIdrifter CS 浮生筆錄](https://hackmd.io/s/rypeUnYSb)<br> Thread Synchronization in Linux
## Create Process & Wait Process
### 常見的Create and Wait
- Create child process
- `fork()` (<unistd.h>)
- `system()`
- exec – 有 `execve()` 等共六個 functions (<unistd.h>)
- Wait child process
- `wait()` & `waidpid()` (<sys/types.h> <sys/wait.h>)
- Wait **other** processes
- 建議 **popen** + **ps** + **grep**
- **popen ??**
- Third-party library -> libprocps
### fork() Theorem
- Unix/Linux 以 `fork()` 產生 process,`exec()` 執行 process
- 呼叫 `fork()` 的為 Parent,產生出的為 Child,運行先後順序無法保證,用 `vfork()` 可以保證 Child 一定先運行 (直到exit 或 exec)
- Child 將完美複製整個 Parent,但由於**太浪費記憶體**,現有系統不再完整複製,只複製變更的部分 (**copy-on-write**)
- Process 結束後會產生結束狀態(程式碼,Exit code,CPU使用時間) 由系統保管,直到 Parent 領回才真正消滅
- 如果 Parent 先結束
- 把 Child 托孤給 **init**
- Parent 用 `wait()` 等待 Child
- Parent 先轉成 **idle**,直到 Child 結束,Parent 領回 Child 的結束狀態後繼續運行
- Parent 活著且不處理 Child 的結束狀態
- Child 變成 **zombie** 且 zombie 一直存在 (占用 Process List)
- Wiki **Hello World**
- On success, the PID of the child process is returned in the parent,
and 0 is returned in the child. On failure, -1 is returned in the
parent, no child process is created, and errno is set appropriately.
```C
int main(void)
{
pid_t pid = fork(); // parenet get child pid, child get 0
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
printf("Hello from the child process!\n");
_exit(EXIT_SUCCESS);
}
else {
int status;
(void)waitpid(pid, &status, 0); // parent wait child pid
}
return EXIT_SUCCESS;
}
```
### How to Prevent Zombine
- 有時候 Parent 正在忙,沒空處理 Child 的結束狀態
- 利用兩次 `fork()`,第一個 child 扮演原本的 parent,第二個 child 扮演真正的 child
- 原本 Parent 結束,兩個 Child 都托孤給 init -> 不會有 **zombie**
```C
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#define COUNT 3
int main()
{
pid_t pid = 0;
printf("parent process %u \n",getpid());
if ((pid = fork()) == 0)
{
int i = 0;
while(i++ < COUNT)
{
printf("first child %u pid %u \n",getpid(), pid);
sleep(1);
}
}
else
{
if ((pid = fork()) == 0)
{
int i = 0;
while(i++ < COUNT)
{
printf("second child %u pid %u \n",getpid(), pid);
sleep(1);
}
}
else
{
int i = 0;
while(i++ < COUNT)
{
printf("parent died %u pid %u \n",getpid(), pid);
sleep(1);
}
}
}
}
```
- output result
```shell
parent process 73802
parent died 73802 pid 73804
first child 73803 pid 0
second child 73804 pid 0
first child 73803 pid 0
parent died 73802 pid 73804
second child 73804 pid 0
parent died 73802 pid 73804
second child 73804 pid 0
first child 73803 pid 0
```
- gdb
```shell
(gdb) info proc
process 73130
cmdline = '/home/aidrifter/a.out'
cwd = '/home/aidrifter'
exe = '/home/aidrifter/a.out'
```
- 利用兩次 `fork()`,第一個 child 扮演原本的第二個child 的 parent,再托孤給 init
```C
if (fork() == 0)
{
pritnf("first child %u \n",getpid());
if (fork() == 0)
pritnf("second child %u \n",getpid());
else
pritnf("first child died %u \n",getpid());
}
else
{
// Parent 領回第一個 child 的結束狀態,
// 再繼續做 Parent 該忙碌的事情
}
```
## Create Thread & Wait Thread
### Create Pthread and Wait
```C
// sucess : return 0
// failed : return EAGAIN,EINVAL,EPERM
int pthread_create ( // 建立一個新的 thread
pthread_t *tid, // 指標指向 thread ID
pthread_attr_t *attr, // thread 屬性設定
void *func(void *), // function name
void *arg); // parameter for function
// sucess : return 0
// failed : return EDEADLK, EINVAL, ESRCH
int pthread_join ( // 等待某個 thread 结束
pthread_t tid, // thread ID
void **retval); // pthread_exit() 回傳值
```
### Hint
- 沒有特別設定,一個 thread 可以被 `pthread_join()` **等待**
- 不想被等待的話有兩種方式
- 建立前設定 `attr`
- `pthread_attr_init()` 做 init,
- `pthread_attr_setdetachstate()` 設定 detach
- `pthread_create()` 傳入 attr
- `pthread_attr_destroy()` 用完 attr 後要記得 free resource
- 建立完後才用 pthread_detach (thread_id);
- #include <pthread.h>
- gcc 的時候參數 `–lpthread`
- Reference
- [POSIX Threads Programming](https://computing.llnl.gov/tutorials/pthreads/)
### basic pthread
- 會先印出 num=110 幾秒過後再印出 x=100
```C
void main(void) {
int x = 100;
pthread_t tid = 0;
if ((pthread_create(&tid, NULL, aa,
(void *)(&x))) != 0)
{
printf (“Create thread failed \n”);
return;
}
pthread_join (tid,NULL);
printf (“x = %d\n”, x);
}
void *aa(void *arg) {
int num = 0;
num = *( (int *)arg );
num = num + 10;
printf (“num = %d\n”, num);
sleep (5);
}
```
## Mutex
- 保證一次只有一個進入
### Mutex relation function in Linux
```C
/* create one Mutex */
int pthread_mutex_init (
pthread_mutex_t *mutex, // Init mutex
pthread_mutexattr_t *attr); // set NULL commonly
/* lock mutex */
int pthread_mutex_lock (
pthread_mutex_t *mutex );
/* release mutex */
int pthread_mutex_unlock (
pthread_mutex_t *mutex );
/* try to lock mutex(no wait) */
int pthread_mutex_trylock (
pthread_mutex_t *mutex );
/*
* release mutex back to OS
* sucess: return 0
* failed: Error num > 0
*
*/
int pthread_mutex_destroy(
pthread_mutex_t *mutex );
```
### Concept : Mutex in Linux(attr)
- 用法跟 Windows 差不多,都是在**保證一次只有一個thread能夠進入**
- 正常狀況下,pthread_mutex_lock() 不允許重複進入 (跟 Window 不同),重複進入將造成 deadlock
- 想要 **Recursive** 时候**repeatly 進入 Mutex**
- 利用 **attr** 設定 `PTHREAD_MUTEX_RECURSIVE`
- 一樣,mutex_lock 幾次,就要相對 mutex_unlock 幾次
- How to use attr in pthread ?
```C
// Initialize attr
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); // set attr
// create Mutex 的時候傳入 attr
pthread_mutex_init (&g_mutex, &attr);
// release attr
pthread_mutexattr_destroy(&attr);
```
- pthread mutex sample code
- mutex is in **user sapce**, some guy create shm to communicate with each thread(**cross-processes**)
```C
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#define gettid() syscall(__NR_gettid)
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
int j = 0;
int aa(void)
{
pthread_mutex_lock (&g_mutex);
j += 2;
printf("j = %u tid %lu \n",j ,gettid());
pthread_mutex_unlock (&g_mutex);
return 0;
}
int bb(void)
{
pthread_mutex_lock (&g_mutex);
j += 3;
printf("j = %u tid %lu\n",j ,gettid());
pthread_mutex_unlock (&g_mutex);
return 0;
}
int main(void)
{
pthread_t tid1,tid2;
pthread_mutex_init(&g_mutex,NULL);
/* create aa & bb threads */
pthread_create (&tid1, NULL, aa, NULL);
pthread_create (&tid2, NULL, bb, NULL);
/* wait aa bb threads */
pthread_join (tid1, NULL);
pthread_join (tid2, NULL);
printf("j = %u tid %lu\n",j ,gettid());
pthread_mutex_destroy(&g_mutex);
return 0;
}
```
- gcc compile parameter
```shell
gcc thread.c -lpthread
```
### Ignore pthread_mutex_init()
- [PTHREAD_MUTEX_INITIALIZER vs pthread_mutex_init ( &mutex, param)](https://stackoverflow.com/questions/14320041/pthread-mutex-initializer-vs-pthread-mutex-init-mutex-param)
```C
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
// lock
pthread_mutex_lock (&g_mutex);
{
/* Critical Section (memory operation)*/
}
// unlock
pthread_mutex_unlock (&g_mutex);
```
## Shared-Exclusive Lock
- 可以同時讀取,不能同時寫入,
也不能同時讀取+寫入
(限定同一個 process 的 threads 共同使用)
- 用在有些 threads 讀取共用資源,有些 threads 修改
- 幾個 threads 可以同時讀取,不會有問題
- 讀取與寫入不可同時發生,靠鎖定保證沒有這樣的狀況
![](https://i.imgur.com/QZsaXxg.png)
## File Lock
- 類似 Shared-Exclusive Lock
可以跨 process 使用
- 利用 fcntl() 實現,fcntl() 功能強大,這只邊介紹 File Lock
- 利用一個檔案本來就可**同時被讀取**,但不能同時**讀取+寫入**
- 需要 `<unistd.h>` , `<fcntl.h>`
- 通常可以用來
- Multi-process 同時使用一個資源狀況下,做到 Read/Write Lock
- 利用 File Lock 性質,在 Multi-process 建立 Critical Section
- 可以當成【如果 Process A 運行中,Process B 不運行】,舉例來說可以做成 Singleton (確保只有一個相同的 Daemon 在運行)
- CMD Parameter
- **F_SETLK**:執行鎖定,鎖定方式由 `stLock` 指定,若無法鎖定將返回EACCES 或 EAGAIN,不等待
- **F_SETLKW**:無法鎖定時會等待,直到等到為止
- **F_GETLK**:取得目前鎖定狀況,結果存放在`stLock`中
- `stLock`.**l_type** 可以是
- F_RDLCK (Read Lock)
- F_WRLCK (Write Lock)
- F_UNLCK (Unlock)
- `stLock` 的 **l_whence**, **l_start**, **l_len** 可指定鎖定 File 的某個範圍
```C
#include <unistd.h>
#include <fcntl.h>
/*
* return value
* sucess: return value accronding to cmd
* failed: -1
*
*/
extern int fcntl (
int fd, // use open() to get File descriptor
int cmd, // F_SETLK, F_SETLKW, F_GETLK
struct flock *stLock); // flock structure
```
## Event
### Condition Variable 相關 functions
```C
// create Condition Variable
int pthread_cond_init (
pthread_cond_t *cond, // Create Condition Variable
pthread_condattr_t *attr ); // 建立的设定,可以给 NULL 就好
// wait some Condition Variable
int pthread_cond_wait (
pthread_cond_t *cond, // Wait Condition Variable
pthread_mutex_t *mutex); // Accompany wait 所使用的 mutex
// 通知一個wait的 thread
int pthread_cond_signal (
pthread_cond_t *cond);
// 通知所有wait的 threads
int pthread_cond_broadcast (
pthread_cond_t *cond);
// release condition
// sucess : return 0
// failed : Error num > 0
int pthread_cond_destroy (pthread_cond_t *cond);
```
### Condition Variable Usage
```C
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_cont_t g_cond;
pthread_mutex_t g_mutex;
void *aa(void *arg)
{
…
/* 做完, signal 通知 (event)*/
pthread_cond_signal (&g_cond,
&g_mutex);
}
main()
{
pthread_t tid;
// init condition
pthread_cond_init(&g_cond, NULL);
pthread_mutex_init(&g_mutex, NULL);
// create thread aa
pthread_create(&tid, NULL, aa, NULL);
…
// wiat until aa terminates
pthread_cond_wait(&g_cond, &g_mutex);
// aa has been finished, we can continue ...
…
// release source
pthread_cond_destroy(&g_cond);
pthread_mutex_destroy(&g_mutex);
}
```
### Signal Function
```C
// clear set
int sigemptyset (sigset_t *set)
// add one signal to set group
int sigaddset (
sigset_t *set, // signal set
int signum); // 要加入的 signal number
// delete one signal form signal set
int sigdelset (
sigset_t *set, // signal set
int signum); // delete signal number
// set waitting singnals
int sigprocmask (
int how, // wait way : SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK
const sigset_t *set, // 打算等待的哪些 signals (signal set)
sigset_t *oldset); // 返回原本的 set,可以是 NULL
// wait set 裡面的all signal(s)
int sigwait (*set, int* sig);
// notice some process -> signum has already happened
int kill (pid, signum);
```
### Use Signal Like Event
```C
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
void *aa(void *arg)
{
pid_t ppid=0;
// Get parent pid
ppid = getppid();
printf(" %s is working \n",__func__);
// aa finished, notice via signal
kill (ppid, SIGUSR1);
}
int main() {
pthread_t tid; sigset_t set;
int sig=0;
// Init set (only wait SIGUSR1)
sigemptyset (&set);
sigaddset (&set, SIGUSR1);
sigprocmask (SIG_SETMASK, &set, NULL);
// Create thread aa
pthread_create (&tid, NULL, aa, NULL);
// Wait aa's signal terminate (SIGUSR1)
sigwait (&set, &sig);
// aa was already finished , continue ...
// …
printf(" %s is working \n",__func__);
return 0;
}
```
- [FIXME] prcoess will terminate tmux when `kill()`
```shell
(gdb) bt
#0 kill () at ../sysdeps/unix/syscall-template.S:84
#1 0x0000555555554952 in aa (arg=0x0) at event.c:17
#2 0x00007ffff7bc06da in start_thread (arg=0x7ffff77f1700) at pthread_create.c:456
#3 0x00007ffff78fad7f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:105
(gdb) c
Continuing.
User defined signal 1
```
## Semaphore
### Semaphore Function
```C
// Create one anonymous Semaphore
// sucess: return 0
// failed: return -1
int sem_init (
sem_t *sem, // 要使用的 Semaphore
int pshared, // 0: 不能跨 proc 使用,非0: 可以跨 proc
unsigned int value ); // 一開始的 resource count
// Create one named Semaphore
// sucess: return 0
// failed: return -1
sem_t sem_open (
const char *name, // 取名字,透過名字共用
int oflag, // O_CREAT,O_CREAT | O_EXCL
mode_t mode, // S_IRWXU,S_IRUSR…,S_IRWXG, S_IWGRP…,S_IRWXO,S_IXOTH…
unsigned int value ); //一開始的 resource count
int sem_post (sem_t *sem); // increase resource. unlimited , never sleep
int sem_wait (sem_t *sem); // decrease resource,sleep when resource count = 0
int sem_trywait (sem_t *sem); // decrease resource,Don't slee[
int sem_getvalue (sem_t *sem, int *sval); // 取得目前 count (放 sval)
int sem_close (sem_t *sem); // close sem_t from sem_open()
int sem_destroy (sem_t *sem); // destory sem_t from sem_init()
int sem_unlink (sem_t *sem); // release sem_t's resource from sem_open()
```
### Semaphore Concept
- 使用 `sem_wait()` or `sem_trywait()` 當消費者
- 消費者一次把 Resource count 減一
- Resource count=0 的時候,消費者睡覺
- 用 `sem_post()` 當生產者
- 生產者不會真的睡覺
- 連上限都沒有
- 生產者要睡覺,要自己想辦法
- 單純使用`sem_init()` + `fork()`,Parent 跟 Child 將不會共用同一個semaphore,要加上 shared memory 配合使用。 ==這是個常見的陷阱==