├── .gitignore ├── src ├── chapter02 │ ├── CMakeLists.txt │ └── thread_lifecycle.c ├── chapter06 │ ├── CMakeLists.txt │ ├── getlogin.c │ ├── flock.c │ ├── putchar.c │ └── atfork.c ├── chapter01 │ ├── CMakeLists.txt │ ├── thread_error.c │ ├── alarm.c │ ├── alarm_fork.c │ └── alarm_thread.c ├── chapter03 │ ├── mutex_static.c │ ├── cond_static.c │ ├── CMakeLists.txt │ ├── mutex_dynamic.c │ ├── cond_dynamic.c │ ├── cond.c │ ├── trylock.c │ ├── alarm_mutex.c │ ├── alarm_cond.c │ └── backoff.c ├── chapter07 │ ├── CMakeLists.txt │ ├── barrier.h │ ├── spinlock.h │ ├── workq.h │ ├── rwlock.h │ ├── spinlock_main.c │ ├── pthread_spinlock.c │ ├── barrier.c │ ├── pthread_semaphore.c │ ├── barrier_main.c │ ├── pthread_barriers.c │ ├── workq_main.c │ ├── rwlock_main.c │ ├── pthread_rwlock.c │ ├── rwlock.c │ └── workq.c ├── lib │ └── errors.h └── chapter05 │ ├── CMakeLists.txt │ ├── cond_attr.c │ ├── mutex_attr.c │ ├── cancel.c │ ├── thread_attr.c │ ├── cancel_disable.c │ ├── once.c │ ├── tsd_once.c │ ├── sched_thread.c │ ├── cancel_async.c │ ├── cancel_cleanup.c │ ├── cancel_subcontract.c │ ├── tsd_destructor.c │ └── sched_attr.c ├── doc ├── images │ ├── lifecycle.png │ ├── pipeline.png │ ├── clinet_server.png │ └── working_group.png ├── 04_a_few_ways_to_threads.md ├── 08_synchronization_essentials.md ├── 06_posix_adjusts_to_threads.md ├── 02_threads.md ├── 07_extended.md ├── 01_overview.md ├── 03_synchronization.md └── 05_advanced_thread_programming.md ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | bin 3 | build -------------------------------------------------------------------------------- /src/chapter02/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_EXECUTABLE(thread_lifecycle thread_lifecycle.c) -------------------------------------------------------------------------------- /doc/images/lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veinin/POSIX-threads-programming-tutorials/HEAD/doc/images/lifecycle.png -------------------------------------------------------------------------------- /doc/images/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veinin/POSIX-threads-programming-tutorials/HEAD/doc/images/pipeline.png -------------------------------------------------------------------------------- /doc/images/clinet_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veinin/POSIX-threads-programming-tutorials/HEAD/doc/images/clinet_server.png -------------------------------------------------------------------------------- /doc/images/working_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veinin/POSIX-threads-programming-tutorials/HEAD/doc/images/working_group.png -------------------------------------------------------------------------------- /src/chapter06/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_EXECUTABLE(atfork atfork.c) 2 | ADD_EXECUTABLE(flock flock.c) 3 | ADD_EXECUTABLE(putchar putchar.c) 4 | ADD_EXECUTABLE(getlogin getlogin.c) -------------------------------------------------------------------------------- /src/chapter01/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_EXECUTABLE(alarm alarm.c) 2 | ADD_EXECUTABLE(alarm_fork alarm_fork.c) 3 | ADD_EXECUTABLE(alarm_thread alarm_thread.c) 4 | ADD_EXECUTABLE(thread_error thread_error.c) -------------------------------------------------------------------------------- /src/chapter03/mutex_static.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | typedef struct my_struct_tag { 5 | pthread_mutex_t mutex; 6 | int value; 7 | } my_struct_t; 8 | 9 | my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, 0}; 10 | 11 | int main() 12 | { 13 | return 0; 14 | } -------------------------------------------------------------------------------- /src/chapter01/thread_error.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | pthread_t thread; 9 | int status; 10 | 11 | status = pthread_join(thread, NULL); 12 | if (status != 0) 13 | fprintf(stderr, "error %d: %s\n", status, strerror(status)); 14 | 15 | return status; 16 | } -------------------------------------------------------------------------------- /src/chapter03/cond_static.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | typedef struct my_struct_tag 5 | { 6 | pthread_mutex_t mutex; 7 | pthread_cond_t cond; 8 | int value; 9 | } my_struct_t; 10 | 11 | my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; 12 | 13 | int main(int argc, char const *argv[]) 14 | { 15 | return 0; 16 | } -------------------------------------------------------------------------------- /src/chapter03/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_EXECUTABLE(mutex_static mutex_static.c) 2 | ADD_EXECUTABLE(mutex_dynamic mutex_dynamic.c) 3 | ADD_EXECUTABLE(alarm_mutex alarm_mutex.c) 4 | ADD_EXECUTABLE(trylock trylock.c) 5 | ADD_EXECUTABLE(backoff backoff.c) 6 | ADD_EXECUTABLE(cond_static cond_static) 7 | ADD_EXECUTABLE(cond_dynamic cond_dynamic.c) 8 | ADD_EXECUTABLE(cond cond.c) 9 | ADD_EXECUTABLE(alarm_cond alarm_cond.c) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | PROJECT(PTHREAD_PRACTICE) 4 | 5 | SET(CMAKE_C_FLAGS "-pthread") 6 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) 7 | 8 | INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/lib) 9 | 10 | ADD_SUBDIRECTORY(src/chapter01) 11 | ADD_SUBDIRECTORY(src/chapter02) 12 | ADD_SUBDIRECTORY(src/chapter03) 13 | ADD_SUBDIRECTORY(src/chapter05) 14 | ADD_SUBDIRECTORY(src/chapter06) 15 | ADD_SUBDIRECTORY(src/chapter07) -------------------------------------------------------------------------------- /src/chapter07/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_EXECUTABLE(barrier_main barrier_main.c barrier.h barrier.c) 2 | ADD_EXECUTABLE(pthread_barriers pthread_barriers.c) 3 | ADD_EXECUTABLE(rwlock_main rwlock_main.c rwlock.h rwlock.c) 4 | ADD_EXECUTABLE(pthread_rwlock pthread_rwlock.c) 5 | ADD_EXECUTABLE(spinlock_main spinlock_main.c spinlock.h) 6 | ADD_EXECUTABLE(pthread_spinlock pthread_spinlock.c) 7 | ADD_EXECUTABLE(pthread_semaphore pthread_semaphore.c) 8 | ADD_EXECUTABLE(workq_main workq_main.c workq.h workq.c) -------------------------------------------------------------------------------- /src/chapter01/alarm.c: -------------------------------------------------------------------------------- 1 | #include "errors.h" 2 | 3 | int main(void) { 4 | int seconds; 5 | char line[128]; 6 | char message[64]; 7 | 8 | while(1) { 9 | printf("Alarm> "); 10 | if (fgets(line, sizeof(line), stdin) == NULL) 11 | exit(0); 12 | 13 | if (strlen(line) == 0) 14 | continue; 15 | 16 | if (sscanf(line, "%d %64[^\n]", &seconds, message) < 2) { 17 | fprintf(stderr, "Bad command\n"); 18 | } else { 19 | sleep(seconds); 20 | printf("(%d) %s\n", seconds, message); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/lib/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef ERRORS_H 2 | #define ERRORS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define err_abort(code, text) \ 11 | do {\ 12 | fprintf(stderr, "%s at \"%s\":%d: %s\n",\ 13 | text, __FILE__, __LINE__, strerror(code));\ 14 | abort();\ 15 | } while(0) 16 | 17 | #define errno_abort(text) \ 18 | do {\ 19 | fprintf(stderr, "%s at \"%s\":%d: %s\n",\ 20 | text, __FILE__, __LINE__, strerror(errno));\ 21 | abort();\ 22 | } while(0) 23 | 24 | #endif // ERRORS_H -------------------------------------------------------------------------------- /src/chapter02/thread_lifecycle.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | void *thread_routine(void *arg) { 5 | return arg; 6 | } 7 | 8 | int main(void) { 9 | pthread_t thread_id; 10 | void *thread_result; 11 | int status; 12 | 13 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 14 | if (status != 0) 15 | err_abort(status, "Create thread"); 16 | 17 | status = pthread_join(thread_id, &thread_result); 18 | if (status != 0) 19 | err_abort(status, "Join thread"); 20 | 21 | if (thread_result == NULL) 22 | return 0; 23 | else 24 | return 1; 25 | } -------------------------------------------------------------------------------- /src/chapter05/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_EXECUTABLE(once once.c) 2 | ADD_EXECUTABLE(mutex_attr mutex_attr.c) 3 | ADD_EXECUTABLE(cond_attr cond_attr.c) 4 | ADD_EXECUTABLE(thread_attr thread_attr.c) 5 | ADD_EXECUTABLE(cancel cancel.c) 6 | ADD_EXECUTABLE(cancel_disable cancel_disable.c) 7 | ADD_EXECUTABLE(cancel_async cancel_async.c) 8 | ADD_EXECUTABLE(cancel_cleanup cancel_cleanup.c) 9 | ADD_EXECUTABLE(cancel_subcontract cancel_subcontract.c) 10 | ADD_EXECUTABLE(tsd_once tsd_once.c) 11 | ADD_EXECUTABLE(tsd_destructor tsd_destructor.c) 12 | ADD_EXECUTABLE(sched_attr sched_attr.c) 13 | ADD_EXECUTABLE(sched_thread sched_thread.c) -------------------------------------------------------------------------------- /src/chapter05/cond_attr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | pthread_cond_t cond; 5 | 6 | int main() 7 | { 8 | int status; 9 | pthread_condattr_t cond_attr; 10 | 11 | status = pthread_condattr_init(&cond_attr); 12 | if (status != 0) 13 | err_abort(status, "Create attr"); 14 | 15 | status = pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_PRIVATE); 16 | if (status != 0) 17 | err_abort(status, "Set pshared"); 18 | 19 | status = pthread_cond_init(&cond, &cond_attr); 20 | if (status != 0) 21 | err_abort(status, "Init cond"); 22 | 23 | return 0; 24 | } -------------------------------------------------------------------------------- /src/chapter03/mutex_dynamic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | typedef struct my_struct_tag { 5 | pthread_mutex_t mutex; 6 | int value; 7 | } my_struct_t; 8 | 9 | int main() 10 | { 11 | my_struct_t *data; 12 | int status; 13 | 14 | data = malloc(sizeof(my_struct_t)); 15 | if (data == NULL) 16 | errno_abort("Allocate structure"); 17 | 18 | status = pthread_mutex_init(&data->mutex, NULL); 19 | if (status != 0) 20 | err_abort(status, "Init mutex"); 21 | 22 | status = pthread_mutex_destroy(&data->mutex); 23 | if (status != 0) 24 | err_abort(status, "Destroy mutex"); 25 | 26 | free(data); 27 | 28 | return status; 29 | } -------------------------------------------------------------------------------- /src/chapter07/barrier.h: -------------------------------------------------------------------------------- 1 | #ifndef BARRIER_H 2 | #define BARRIER_H 3 | 4 | #include 5 | 6 | typedef struct barrier_tag { 7 | pthread_mutex_t mutex; 8 | pthread_cond_t cv; 9 | int valid; 10 | int threshold; 11 | int counter; 12 | unsigned long cycle; 13 | } barrier_t; 14 | 15 | #define BARRIER_VALID 0xdbcafe 16 | 17 | #define BARRIER_INITIALIZER(cnt) \ 18 | {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, \ 19 | BARRIER_VALID, cnt, cnt, 0} 20 | 21 | int barrier_init(barrier_t *barrier, int count); 22 | int barrier_destroy(barrier_t *barrier); 23 | int barrier_wait(barrier_t *barrier); 24 | 25 | #endif //BARRIER_H -------------------------------------------------------------------------------- /src/chapter07/spinlock.h: -------------------------------------------------------------------------------- 1 | #ifndef SPINLOCK_H 2 | #define SPINLOCK_H 3 | 4 | typedef struct spinlock_tag { 5 | int lock; 6 | } spinlock_t; 7 | 8 | static inline void spinlock_init(spinlock_t *sl) 9 | { 10 | sl->lock = 0; 11 | } 12 | 13 | static inline void spinlock_lock(spinlock_t *sl) 14 | { 15 | while(__sync_lock_test_and_set(&sl->lock, 1)) {} 16 | } 17 | 18 | static inline int spinlock_trylock(spinlock_t *sl) 19 | { 20 | return __sync_lock_test_and_set(&sl->lock, 1) == 0; 21 | } 22 | 23 | static inline void spinlock_unlock(spinlock_t *sl) 24 | { 25 | __sync_lock_release(&sl->lock); 26 | } 27 | 28 | static inline void spinlock_destroy(spinlock_t *sl) 29 | { 30 | (void) sl; 31 | } 32 | 33 | #endif //SPINLOCK_H -------------------------------------------------------------------------------- /src/chapter06/getlogin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | int main() 5 | { 6 | char login_str[LOGIN_NAME_MAX]; 7 | char stdin_str[TTY_NAME_MAX]; 8 | char cterm_str[L_ctermid], *cterm_str_ptr; 9 | int status; 10 | 11 | printf("%d %d %d", LOGIN_NAME_MAX, TTY_NAME_MAX, L_ctermid); 12 | 13 | status = getlogin_r(login_str, sizeof(login_str)); 14 | if (status != 0) 15 | err_abort(status, "Get login"); 16 | 17 | cterm_str_ptr = ctermid(cterm_str); 18 | if (cterm_str_ptr == NULL) 19 | errno_abort("Get cterm"); 20 | 21 | status = ttyname_r(0, stdin_str, sizeof(stdin_str)); 22 | if(status != 0) 23 | err_abort(status, "Get stdin"); 24 | 25 | printf("User: %s, cterm: %s, fd 0: %s\n", login_str, cterm_str, stdin_str); 26 | return 0; 27 | } -------------------------------------------------------------------------------- /src/chapter07/workq.h: -------------------------------------------------------------------------------- 1 | #ifndef WORKQ_H 2 | #define WORKQ_H 3 | 4 | #include 5 | 6 | typedef struct workq_ele_tag { 7 | struct workq_ele_tag *next; 8 | void *data; 9 | } workq_ele_t; 10 | 11 | typedef struct workq_tag { 12 | pthread_mutex_t mutex; 13 | pthread_cond_t cv; 14 | pthread_attr_t attr; 15 | workq_ele_t *first, *last; 16 | int valid; 17 | int quit; 18 | int parallelism; 19 | int counter; 20 | int idle; 21 | void (*engine)(void *); 22 | } workq_t; 23 | 24 | #define WORKQ_VALID 0xdec2018 25 | 26 | int workq_init(workq_t *wq, int threads, void (*engine)(void *)); 27 | int workq_destroy(workq_t *wq); 28 | int workq_add(workq_t *wq, void *data); 29 | 30 | #endif -------------------------------------------------------------------------------- /src/chapter05/mutex_attr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | pthread_mutex_t mutex; 5 | 6 | int main() 7 | { 8 | int status; 9 | int pshared; 10 | pthread_mutexattr_t mutex_attr; 11 | 12 | status = pthread_mutexattr_init(&mutex_attr); 13 | if (status != 0) 14 | err_abort(status, "Init mutex attr"); 15 | 16 | status = pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); 17 | if (status != 0) 18 | err_abort(status, "Set pshared"); 19 | 20 | status = pthread_mutex_init(&mutex, &mutex_attr); 21 | if (status != 0) 22 | err_abort(status, "Init mutex"); 23 | 24 | status = pthread_mutexattr_getpshared(&mutex_attr, &pshared); 25 | if (status != 0) 26 | err_abort(status, "Get pshared"); 27 | 28 | printf("pshared: %d\n", pshared); 29 | 30 | return 0; 31 | } -------------------------------------------------------------------------------- /src/chapter01/alarm_fork.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "errors.h" 4 | 5 | int main(void) { 6 | pid_t pid; 7 | int seconds; 8 | char line[128]; 9 | char message[64]; 10 | 11 | while(1) { 12 | printf("Alarm> "); 13 | if (fgets(line, sizeof(line), stdin) == NULL) 14 | exit(0); 15 | 16 | if (strlen(line) <= 1) 17 | continue; 18 | 19 | if (sscanf(line, "%d %64[^\n]", &seconds, message) < 2) { 20 | fprintf(stderr, "Bad command\n"); 21 | } else { 22 | pid = fork(); 23 | if (pid == -1) 24 | errno_abort("Fork"); 25 | 26 | if (pid == (pid_t)0) { 27 | sleep(seconds); 28 | printf("(%d) %s\n", seconds, message); 29 | exit(0); 30 | } else { 31 | do { 32 | pid = waitpid((pid_t)-1, NULL, WNOHANG); 33 | if (pid == (pid_t) -1) 34 | errno_abort("Wait for child"); 35 | } while(pid != (pid_t)0); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/chapter07/rwlock.h: -------------------------------------------------------------------------------- 1 | #ifndef RW_LOCK_H 2 | #define RW_LOCK_H 3 | 4 | #include 5 | 6 | typedef struct rwlock_tag { 7 | pthread_mutex_t mutex; 8 | pthread_cond_t read; 9 | pthread_cond_t write; 10 | int valid; 11 | int r_active; 12 | int w_active; 13 | int r_wait; 14 | int w_wait; 15 | } rwlock_t; 16 | 17 | #define RWLOCK_VALID 0xfacade 18 | 19 | #define RWL_INITIALIZER \ 20 | {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, \ 21 | PTHREAD_COND_INITIALIZER, RWLOCK_VALID, 0, 0, 0, 0} 22 | 23 | int rwl_init(rwlock_t *rwlock); 24 | int rwl_destroy(rwlock_t *rwlock); 25 | int rwl_readlock(rwlock_t *rwlock); 26 | int rwl_readtrylock(rwlock_t *rwlock); 27 | int rwl_readunlock(rwlock_t *rwlock); 28 | int rwl_writelock(rwlock_t *rwlock); 29 | int rwl_writetrylock(rwlock_t *rwlock); 30 | int rwl_writeunlock(rwlock_t *rwlock); 31 | 32 | #endif -------------------------------------------------------------------------------- /doc/04_a_few_ways_to_threads.md: -------------------------------------------------------------------------------- 1 | # 使用线程的几种方式 2 | 3 | ## 线程编程模型 4 | 5 | 线程变成模型主要有以下3种: 6 | 7 | - 流水线。每个线程反复地在数据系列集上执行冋一种操作,并把操作结果传递给下一步驟的其他线程。 8 | - 工作组。每个线程在自己的数据上执行操作,工作组中的线程可能执行同样的操作,也可能执行不同的操作,但是它们一定独立地执行。 9 | - 客户端/服务器。一个客户为每一件工作与一个独立的服务器“订契约”。通常“订契约"是匿名的,即一个请求通过某种接提交。 10 | 11 | ## 流水线 12 | 13 | 在流水线(pipeline) 方式中,“数据元素”流串行地被一组线程顺序处理。每个线程依次在每个元素上执行一个特定的操作,并将结果传递给流 14 | 水线中的下一个线程。 15 | 16 | ![pipeline](images/pipeline.png) 17 | 18 | 例如, 数据可能是扫描的图像,线程 A 可能处理一个图像数组,线程 B 可能在处理的数据中搜索某个特定的属性集,而线程 C 可能控制从线程 B 中收集连续的搜索结果流井做出报告。或者每个线程可能执行某个数据修改序列中的一步。 19 | 20 | ## 工作组 21 | 22 | 在工作组模式中,数据由一组线程分别独立地处理。循环的“并行分解” 通常就是属于这种模式。 23 | 24 | ![working_group](images/working_group.png) 25 | 26 | 例如,可能建立一组线程,每个线程负责处理数组的某些行或列。单一数据集合在线程间分离成不同部分,且结果是一个数据集。 27 | 由于所有的工作线程在不同的数据部分上执行相同的操作,这种模式通常被称为 SIMD (single instruction, multiple data, 单指令多数据流)并行处理。 28 | 29 | ## 客户/服务器 30 | 31 | 在客户服务器系统中,客户请求服务器对一组数据执行某个操作。服务器独立地执行操作——客户端或者等待服务器执行,或者并行地执行并在后面需要时査找结果。尽管让客户等待是最简单的,但这种方式很少有用,因为它不会为客户带来性能上的提高。另一方面,这又是一种对某些公共资源同步管理的简单方式。 32 | 33 | ![clinet_server](images/clinet_server.png) -------------------------------------------------------------------------------- /src/chapter03/cond_dynamic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | typedef struct my_struct_tag 5 | { 6 | pthread_mutex_t mutex; 7 | pthread_cond_t cond; 8 | int value; 9 | } my_struct_t; 10 | 11 | int main(int argc, char const *argv[]) 12 | { 13 | my_struct_t *data; 14 | int status; 15 | 16 | data = malloc(sizeof(my_struct_t)); 17 | if (data == NULL) 18 | errno_abort("Allocate structure"); 19 | 20 | status = pthread_mutex_init(&data->mutex, NULL); 21 | if (status != 0) 22 | err_abort(status, "Init mutex"); 23 | 24 | status = pthread_cond_init(&data->cond, NULL); 25 | if (status != 0) 26 | err_abort(status, "Init condition"); 27 | 28 | status = pthread_cond_destroy(&data->cond); 29 | if (status != 0) 30 | err_abort(status, "Destroy condition"); 31 | 32 | status = pthread_mutex_destroy(&data->mutex); 33 | if (status != 0) 34 | err_abort(status, "Destroy mutex"); 35 | 36 | free(data); 37 | 38 | return status; 39 | } 40 | -------------------------------------------------------------------------------- /src/chapter05/cancel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | static int counter; 5 | 6 | void *thread_routine(void *arg) 7 | { 8 | printf("thread_routine starting\n"); 9 | for (counter == 0; ; counter++) 10 | { 11 | if ((counter % 1000) == 0) 12 | { 13 | printf("calling testcancel\n"); 14 | pthread_testcancel(); 15 | } 16 | } 17 | } 18 | 19 | int main() 20 | { 21 | pthread_t thread_id; 22 | void *result; 23 | int status; 24 | 25 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 26 | if (status != 0) 27 | err_abort(status, "Create thread"); 28 | 29 | sleep(2); 30 | 31 | printf("callling cancel\n"); 32 | 33 | status = pthread_cancel(thread_id); 34 | if (status != 0) 35 | err_abort(status, "Cancel thread"); 36 | 37 | printf("calling join\n"); 38 | 39 | status = pthread_join(thread_id, &result); 40 | if (status != 0) 41 | err_abort(status, "Join thread"); 42 | 43 | if (result == PTHREAD_CANCELED) 44 | printf("Thread canceled at iteration %d\n", counter); 45 | else 46 | printf("Thread was not canceled\n"); 47 | 48 | return 0; 49 | } -------------------------------------------------------------------------------- /src/chapter01/alarm_thread.c: -------------------------------------------------------------------------------- 1 | #include "errors.h" 2 | #include 3 | 4 | typedef struct alarm_tag { 5 | int seconds; 6 | char message[64]; 7 | } alarm_t; 8 | 9 | void *alarm_thread(void *arg) { 10 | alarm_t *alarm = (alarm_t*)arg; 11 | int status; 12 | 13 | status = pthread_detach(pthread_self()); 14 | if (status != 0) 15 | err_abort(status, "Detach thread"); 16 | 17 | sleep(alarm->seconds); 18 | printf("(%d) %s\n", alarm->seconds, alarm->message); 19 | free(alarm); 20 | return NULL; 21 | } 22 | 23 | int main(void) { 24 | int status; 25 | int seconds; 26 | char line[128]; 27 | alarm_t *alarm; 28 | pthread_t thread; 29 | 30 | while(1) { 31 | printf("Alarm> "); 32 | if (fgets(line, sizeof(line), stdin) == NULL) 33 | exit(0); 34 | 35 | if (strlen(line) <= 1) 36 | continue; 37 | 38 | alarm = (alarm_t*)malloc(sizeof(alarm_t)); 39 | if (alarm == NULL) 40 | errno_abort("Allocate alarm"); 41 | 42 | if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) { 43 | fprintf(stderr, "Bad command\n"); 44 | free(alarm); 45 | } else { 46 | status = pthread_create(&thread, NULL, alarm_thread, alarm); 47 | if (status != 0) 48 | err_abort(status, "Create alarm thread"); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/chapter05/thread_attr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "errors.h" 4 | 5 | void *thread_routine(void *arg) 6 | { 7 | printf("The thread is here\n"); 8 | return NULL; 9 | } 10 | 11 | int main() 12 | { 13 | pthread_t thread_id; 14 | pthread_attr_t thread_attr; 15 | struct sched_param thread_param; 16 | size_t stack_size; 17 | int status; 18 | 19 | status = pthread_attr_init(&thread_attr); 20 | if (status != 0) 21 | err_abort(status, "Create attr"); 22 | 23 | status = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); 24 | if (status != 0) 25 | err_abort(status, "Set detach"); 26 | 27 | #ifdef _POSIX_THREAD_ATTR_STACKSIZE 28 | status = pthread_attr_getstacksize(&thread_attr, &stack_size); 29 | if (status != 0) 30 | err_abort(status, "Get stack size"); 31 | 32 | printf("Default stack size is %zu; minimum is %u\n", stack_size, PTHREAD_STACK_MIN); 33 | 34 | status = pthread_attr_setstacksize(&thread_attr, PTHREAD_STACK_MIN*2); 35 | if (status != 0) 36 | err_abort(status, "Set stack size"); 37 | #endif 38 | 39 | status = pthread_create(&thread_id, &thread_attr, thread_routine, NULL); 40 | if (status != 0) 41 | err_abort(status, "Create thread"); 42 | 43 | printf("Main exiting\n"); 44 | pthread_exit(NULL); 45 | return 0; 46 | } -------------------------------------------------------------------------------- /src/chapter06/flock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define THREADS 3 5 | 6 | void *prompt_routine(void *arg) 7 | { 8 | int prompt = *(int*)arg; 9 | char *string; 10 | int len; 11 | 12 | string = (char*)malloc(128); 13 | if (string == NULL) 14 | errno_abort("Alloc string"); 15 | 16 | flockfile(stdin); 17 | flockfile(stdout); 18 | 19 | printf("Thread %d> ", prompt); 20 | if (fgets(string, 127, stdin) == NULL) 21 | string[0] = '\0'; 22 | else { 23 | len = strlen(string); 24 | if (len > 0 && string[len - 1] == '\n') 25 | string[len -1] = '\0'; 26 | } 27 | 28 | funlockfile(stdout); 29 | funlockfile(stdin); 30 | return (void*)string; 31 | } 32 | 33 | int main() 34 | { 35 | pthread_t threads[THREADS]; 36 | int count; 37 | void *string; 38 | int status; 39 | 40 | for (count = 0; count < THREADS; count++) { 41 | status = pthread_create(&threads[count], NULL, prompt_routine, &count); 42 | if (status != 0) 43 | err_abort(status, "Create thread"); 44 | } 45 | 46 | for (count = 0; count < THREADS; count++) { 47 | status = pthread_join(threads[count], &string); 48 | if (status != 0) 49 | err_abort(status, "Join thread"); 50 | 51 | printf("Thread %d: \"%s\"\n", count, (char*)string); 52 | free(string); 53 | } 54 | 55 | return 0; 56 | } -------------------------------------------------------------------------------- /src/chapter06/putchar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | void *lock_routine(void *arg) 5 | { 6 | char *pointer; 7 | 8 | flockfile(stdout); 9 | for (pointer = arg; *pointer != '\0'; pointer++) { 10 | putchar_unlocked(*pointer); 11 | sleep(1); 12 | } 13 | funlockfile(stdout); 14 | return NULL; 15 | } 16 | 17 | void *unlock_routine(void *arg) 18 | { 19 | char *pointer; 20 | 21 | for (pointer = arg; *pointer != '\0'; pointer++) { 22 | putchar_unlocked(*pointer); 23 | sleep(1); 24 | } 25 | return NULL; 26 | } 27 | 28 | int main(int argc, char *argv[]) 29 | { 30 | pthread_t thread1, thread2, thread3; 31 | int flock_flag = 1; 32 | int status; 33 | void *(*thread_func)(void *); 34 | 35 | if (argc > 1) 36 | flock_flag = atoi(argv[1]); 37 | 38 | if (flock_flag) 39 | thread_func = lock_routine; 40 | else 41 | thread_func = unlock_routine; 42 | 43 | status = pthread_create(&thread1, NULL, thread_func, "this is thread 1\n"); 44 | if (status != 0) 45 | err_abort(status, "Create thread"); 46 | 47 | status = pthread_create(&thread2, NULL, thread_func, "this is thread 2\n"); 48 | if (status != 0) 49 | err_abort(status, "Create thread"); 50 | 51 | status = pthread_create(&thread3, NULL, thread_func, "this is thread 3\n"); 52 | if (status != 0) 53 | err_abort(status, "Create thread"); 54 | 55 | pthread_exit(NULL); 56 | } -------------------------------------------------------------------------------- /src/chapter07/spinlock_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | #include "spinlock.h" 4 | 5 | #define THREADS 10 6 | #define ITERATIONS 100000 7 | 8 | typedef struct thread_tag { 9 | int thread_num; 10 | pthread_t thread_id; 11 | } thread_t; 12 | 13 | thread_t threads[THREADS]; 14 | spinlock_t spl; 15 | int updates = 0; 16 | 17 | void *thread_routine(void *arg) 18 | { 19 | thread_t *self = (thread_t*)arg; 20 | int iteration; 21 | 22 | for (iteration = 0; iteration < ITERATIONS; iteration++) { 23 | spinlock_lock(&spl); 24 | 25 | updates++; 26 | 27 | spinlock_unlock(&spl); 28 | } 29 | 30 | return NULL; 31 | } 32 | 33 | int main() 34 | { 35 | int status; 36 | int count; 37 | int thread_updates = 0; 38 | unsigned int seed = 1; 39 | 40 | spinlock_init(&spl); 41 | 42 | for (count = 0; count < THREADS; count++) { 43 | threads[count].thread_num = count; 44 | status = pthread_create(&threads[count].thread_id, 45 | NULL, thread_routine, (void *)&threads[count]); 46 | if (status != 0) 47 | err_abort(status, "Create thread"); 48 | } 49 | 50 | for (count = 0; count < THREADS; count++) { 51 | status = pthread_join(threads[count].thread_id, NULL); 52 | if (status != 0) 53 | err_abort(status, "Join thread"); 54 | } 55 | 56 | spinlock_destroy(&spl); 57 | 58 | printf("%d data updates\n", updates); 59 | 60 | return 0; 61 | } -------------------------------------------------------------------------------- /src/chapter07/pthread_spinlock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define THREADS 10 5 | #define ITERATIONS 100000 6 | 7 | typedef struct thread_tag { 8 | int thread_num; 9 | pthread_t thread_id; 10 | } thread_t; 11 | 12 | thread_t threads[THREADS]; 13 | pthread_spinlock_t spl; 14 | int updates = 0; 15 | 16 | void *thread_routine(void *arg) 17 | { 18 | thread_t *self = (thread_t*)arg; 19 | int iteration; 20 | 21 | for (iteration = 0; iteration < ITERATIONS; iteration++) { 22 | pthread_spin_lock(&spl); 23 | 24 | updates++; 25 | 26 | pthread_spin_unlock(&spl); 27 | } 28 | 29 | return NULL; 30 | } 31 | 32 | int main() 33 | { 34 | int status; 35 | int count; 36 | int thread_updates = 0; 37 | unsigned int seed = 1; 38 | 39 | pthread_spin_init(&spl, PTHREAD_PROCESS_PRIVATE); 40 | 41 | for (count = 0; count < THREADS; count++) { 42 | threads[count].thread_num = count; 43 | status = pthread_create(&threads[count].thread_id, 44 | NULL, thread_routine, (void *)&threads[count]); 45 | if (status != 0) 46 | err_abort(status, "Create thread"); 47 | } 48 | 49 | for (count = 0; count < THREADS; count++) { 50 | status = pthread_join(threads[count].thread_id, NULL); 51 | if (status != 0) 52 | err_abort(status, "Join thread"); 53 | } 54 | 55 | pthread_spin_destroy(&spl); 56 | 57 | printf("%d data updates\n", updates); 58 | 59 | return 0; 60 | } -------------------------------------------------------------------------------- /src/chapter05/cancel_disable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | static int counter; 5 | 6 | void *thread_routine(void *arg) 7 | { 8 | int state; 9 | int status; 10 | 11 | printf("thread_routine starting\n"); 12 | for (counter == 0;; counter++) 13 | { 14 | if ((counter % 755) == 0) 15 | { 16 | status = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); 17 | if (status != 0) 18 | err_abort(status, "Disable cancel"); 19 | 20 | sleep(1); 21 | 22 | status = pthread_setcancelstate(state, &state); 23 | if (status != 0) 24 | err_abort(status, "Restore cancel"); 25 | } 26 | else if ((counter % 1000) == 0) 27 | { 28 | printf("calling testcancel\n"); 29 | pthread_testcancel(); 30 | } 31 | } 32 | } 33 | 34 | int main() 35 | { 36 | pthread_t thread_id; 37 | void *result; 38 | int status; 39 | 40 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 41 | if (status != 0) 42 | err_abort(status, "Create thread"); 43 | 44 | sleep(2); 45 | 46 | printf("callling cancel\n"); 47 | 48 | status = pthread_cancel(thread_id); 49 | if (status != 0) 50 | err_abort(status, "Cancel thread"); 51 | 52 | printf("calling join\n"); 53 | 54 | status = pthread_join(thread_id, &result); 55 | if (status != 0) 56 | err_abort(status, "Join thread"); 57 | 58 | if (result == PTHREAD_CANCELED) 59 | printf("Thread canceled at iteration %d\n", counter); 60 | else 61 | printf("Thread was not canceled\n"); 62 | 63 | return 0; 64 | } -------------------------------------------------------------------------------- /src/chapter05/once.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | pthread_once_t once_block = PTHREAD_ONCE_INIT; 5 | pthread_mutex_t mutex; 6 | 7 | void once_init_routine(void) 8 | { 9 | int status; 10 | 11 | status = pthread_mutex_init(&mutex, NULL); 12 | if (status != 0) 13 | err_abort(status, "Init mutex"); 14 | } 15 | 16 | void *thread_routine(void *arg) 17 | { 18 | int status; 19 | 20 | status = pthread_once(&once_block, once_init_routine); 21 | if (status != 0) 22 | err_abort(status, "Once init"); 23 | 24 | status = pthread_mutex_lock(&mutex); 25 | if (status != 0) 26 | err_abort(status, "Init mutex"); 27 | 28 | printf("thread_toutine has locked the mutex.\n"); 29 | 30 | status = pthread_mutex_unlock(&mutex); 31 | if (status != 0) 32 | err_abort(status, "Destroy mutex"); 33 | 34 | return NULL; 35 | } 36 | 37 | int main() 38 | { 39 | int status; 40 | pthread_t thread_id; 41 | 42 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 43 | if (status != 0) 44 | err_abort(status, "Create thread"); 45 | 46 | status = pthread_once(&once_block, once_init_routine); 47 | if (status != 0) 48 | err_abort(status, "Once init"); 49 | 50 | status = pthread_mutex_lock(&mutex); 51 | if (status != 0) 52 | err_abort(status, "Init mutex"); 53 | 54 | printf("Main has locked the mutex.\n"); 55 | 56 | status = pthread_mutex_unlock(&mutex); 57 | if (status != 0) 58 | err_abort(status, "Destroy mutex"); 59 | 60 | status = pthread_join(thread_id, NULL); 61 | if (status != 0) 62 | err_abort(status, "Join thread"); 63 | 64 | return 0; 65 | } -------------------------------------------------------------------------------- /src/chapter05/tsd_once.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | typedef struct tsd_tag { 5 | pthread_t thread_id; 6 | char *string; 7 | } tsd_t; 8 | 9 | pthread_key_t tsd_key; 10 | pthread_once_t key_one = PTHREAD_ONCE_INIT; 11 | 12 | void once_routine(void) 13 | { 14 | int status; 15 | 16 | printf("initializing key\n"); 17 | status = pthread_key_create(&tsd_key, NULL); 18 | if (status != 0) 19 | err_abort(status, "Create key"); 20 | } 21 | 22 | void *thread_routine(void *arg) 23 | { 24 | tsd_t *value; 25 | int status; 26 | 27 | status = pthread_once(&key_one, once_routine); 28 | if (status != 0) 29 | err_abort(status, "Once init"); 30 | 31 | value = (tsd_t*)malloc(sizeof(tsd_t)); 32 | if (value == NULL) 33 | errno_abort("Allocate key value"); 34 | 35 | status = pthread_setspecific(tsd_key, value); 36 | if (status != 0) 37 | err_abort(status, "Set tsd"); 38 | 39 | printf("%s set tsd value %p\n", (char*)arg, value); 40 | 41 | value->thread_id = pthread_self(); 42 | value->string = (char*)arg; 43 | 44 | value = (tsd_t*)pthread_getspecific(tsd_key); 45 | printf("%s starting...\n", value->string); 46 | 47 | sleep(2); 48 | 49 | value = (tsd_t*)pthread_getspecific(tsd_key); 50 | printf("%s done...\n", value->string); 51 | 52 | return NULL; 53 | } 54 | 55 | int main() 56 | { 57 | int status; 58 | pthread_t thread1, thread2; 59 | 60 | status = pthread_create(&thread1, NULL, thread_routine, "thread 1"); 61 | if (status != 0) 62 | err_abort(status, "Create thread 1"); 63 | 64 | status = pthread_create(&thread2, NULL, thread_routine, "thread 2"); 65 | if (status != 0) 66 | err_abort(status, "Create thread 2"); 67 | 68 | pthread_exit(NULL); 69 | } -------------------------------------------------------------------------------- /src/chapter03/cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "errors.h" 4 | 5 | typedef struct my_struct_tag 6 | { 7 | pthread_mutex_t mutex; 8 | pthread_cond_t cond; 9 | int value; 10 | } my_struct_t; 11 | 12 | my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; 13 | 14 | int hibernation = 1; 15 | 16 | void *wait_thread(void *arg) 17 | { 18 | int status; 19 | 20 | sleep(hibernation); 21 | 22 | status = pthread_mutex_lock(&data.mutex); 23 | if (status != 0) 24 | err_abort(status, "Lock mutex"); 25 | 26 | data.value = 1; 27 | 28 | status = pthread_cond_signal(&data.cond); 29 | if (status != 0) 30 | err_abort(status, "Signal condition"); 31 | 32 | status = pthread_mutex_unlock(&data.mutex); 33 | if (status != 0) 34 | err_abort(status, "Unlock mutex"); 35 | 36 | return NULL; 37 | } 38 | 39 | int main(int argc, char *argv[]) 40 | { 41 | int status; 42 | pthread_t wait_thread_id; 43 | struct timespec timeout; 44 | 45 | if (argc > 1) 46 | hibernation = atoi(argv[1]); 47 | 48 | status = pthread_create(&wait_thread_id, NULL, wait_thread, NULL); 49 | if (status != 0) 50 | err_abort(status, "Create wait thread"); 51 | 52 | timeout.tv_sec = time(NULL) + 2; 53 | timeout.tv_nsec = 0; 54 | 55 | status = pthread_mutex_lock(&data.mutex); 56 | if (status != 0) 57 | err_abort(status, "Lock mutex"); 58 | 59 | while (data.value == 0) { 60 | status = pthread_cond_timedwait(&data.cond, &data.mutex, &timeout); 61 | if (status == ETIMEDOUT) { 62 | printf("Condition wait time out.\n"); 63 | break; 64 | } else if (status != 0) 65 | err_abort(status, "Wait on condition"); 66 | } 67 | 68 | status = pthread_mutex_unlock(&data.mutex); 69 | if (status != 0) 70 | err_abort(status, "Unlock mutex"); 71 | 72 | return 0; 73 | } -------------------------------------------------------------------------------- /src/chapter05/sched_thread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "errors.h" 5 | 6 | #define THREADS 5 7 | 8 | typedef struct thread_tag { 9 | int index; 10 | pthread_t id; 11 | } thread_t; 12 | 13 | thread_t threads[THREADS]; 14 | int rr_min_priority; 15 | 16 | void *thread_routine(void *arg) 17 | { 18 | thread_t *self = (thread_t*)arg; 19 | int my_policy; 20 | struct sched_param my_param; 21 | int status; 22 | 23 | my_param.sched_priority = rr_min_priority + self->index; 24 | printf("Thread %d will set SCHED_FIFO, priority %d\n", self->index, my_param.sched_priority); 25 | 26 | status = pthread_setschedparam(self->id, SCHED_RR, &my_param); 27 | if (status != 0) 28 | err_abort(status, "Set sched"); 29 | 30 | status = pthread_getschedparam(self->id, &my_policy, &my_param); 31 | if (status != 0) 32 | err_abort(status, "Get sched"); 33 | 34 | printf("thread_routine %d running at %s/%d\n", 35 | self->index, 36 | (my_policy == SCHED_FIFO) ? "FIFO" 37 | : (my_policy == SCHED_RR ? "RR" 38 | : (my_policy == SCHED_OTHER ? "OTHER" 39 | : "unknown")), 40 | my_param.sched_priority); 41 | 42 | return NULL; 43 | } 44 | 45 | int main() 46 | { 47 | int count, status; 48 | 49 | rr_min_priority = sched_get_priority_min(SCHED_RR); 50 | if (rr_min_priority == -1) 51 | errno_abort("Get SCHED_RR min priority"); 52 | 53 | for (count = 0; count < THREADS; count++) { 54 | threads[count].index = count; 55 | status = pthread_create(&threads[count].id, NULL, thread_routine, (void *)&threads[count]); 56 | if (status != 0) 57 | err_abort(status, "Create thread"); 58 | } 59 | 60 | for (count = 0; count < THREADS; count++) { 61 | status = pthread_join(threads[count].id, NULL); 62 | if (status != 0) 63 | err_abort(status, "Join thread"); 64 | } 65 | printf("Main exiting\n"); 66 | return 0; 67 | } -------------------------------------------------------------------------------- /src/chapter05/cancel_async.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define SIZE 10 5 | 6 | static int matrixa[SIZE][SIZE]; 7 | static int matrixb[SIZE][SIZE]; 8 | static int matrixc[SIZE][SIZE]; 9 | 10 | void *thread_routine(void *arg) 11 | { 12 | int cancel_type, status; 13 | int i, j, k, value = 1; 14 | 15 | for (i = 0; i < SIZE; i++) 16 | for (j = 0; j < SIZE; j++) 17 | { 18 | matrixa[i][j] = i; 19 | matrixb[i][j] = j; 20 | } 21 | 22 | while (1) { 23 | status = pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, &cancel_type); 24 | if (status != 0) 25 | err_abort(status, "Set cancel type"); 26 | 27 | for (i = 0; i < SIZE; i++) 28 | { 29 | for (j = 0; j < SIZE; j++) 30 | { 31 | matrixc[i][j]= 0; 32 | for (k = 0; k < SIZE; k++) 33 | matrixc[i][j] += matrixa[i][k] * matrixb[k][j]; 34 | } 35 | } 36 | 37 | status = pthread_setcancelstate(cancel_type, &cancel_type); 38 | if (status != 0) 39 | err_abort(status, "Set cancel type"); 40 | 41 | for (i = 0; i < SIZE; i++) 42 | for (j = 0; j < SIZE; j++) 43 | matrixa[i][j] = matrixc[i][j]; 44 | } 45 | } 46 | 47 | int main() 48 | { 49 | pthread_t thread_id; 50 | void *result; 51 | int status; 52 | 53 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 54 | if (status != 0) 55 | err_abort(status, "Create thread"); 56 | 57 | sleep(1); 58 | 59 | printf("callling cancel\n"); 60 | 61 | status = pthread_cancel(thread_id); 62 | if (status != 0) 63 | err_abort(status, "Cancel thread"); 64 | 65 | printf("calling join\n"); 66 | 67 | status = pthread_join(thread_id, &result); 68 | if (status != 0) 69 | err_abort(status, "Join thread"); 70 | 71 | if (result == PTHREAD_CANCELED) 72 | printf("Thread canceled\n"); 73 | else 74 | printf("Thread was not canceled\n"); 75 | 76 | return 0; 77 | } -------------------------------------------------------------------------------- /src/chapter05/cancel_cleanup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define THREADS 5 5 | 6 | typedef struct control_tag 7 | { 8 | int counter, bysy; 9 | pthread_mutex_t mutex; 10 | pthread_cond_t cv; 11 | } control_t; 12 | 13 | control_t control = {0, 1, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER}; 14 | 15 | void cleanup_handler(void *arg) 16 | { 17 | control_t *st = (control_t *)arg; 18 | int status; 19 | 20 | st->counter--; 21 | printf("cleaup_handler: counter == %d\n", st->counter); 22 | 23 | status = pthread_mutex_unlock(&st->mutex); 24 | if (status != 0) 25 | err_abort(status, "Unlock in cleanup handler"); 26 | } 27 | 28 | void *thread_routine(void *arg) 29 | { 30 | int status; 31 | 32 | pthread_cleanup_push(cleanup_handler, (void *)&control); 33 | 34 | status = pthread_mutex_lock(&control.mutex); 35 | if (status != 0) 36 | err_abort(status, "Mutex lock"); 37 | 38 | control.counter++; 39 | 40 | while (control.bysy) 41 | { 42 | status = pthread_cond_wait(&control.cv, &control.mutex); 43 | if (status != 0) 44 | err_abort(status, "Wait on condition"); 45 | } 46 | 47 | pthread_cleanup_pop(1); 48 | return NULL; 49 | } 50 | 51 | int main() 52 | { 53 | pthread_t thread_id[THREADS]; 54 | int count; 55 | void *result; 56 | int status; 57 | 58 | for (count = 0; count < THREADS; count++) 59 | { 60 | status = pthread_create(&thread_id[count], NULL, thread_routine, NULL); 61 | if (status != 0) 62 | err_abort(status, "Create thread"); 63 | } 64 | 65 | sleep(2); 66 | 67 | for (count = 0; count < THREADS; count++) 68 | { 69 | status = pthread_cancel(thread_id[count]); 70 | if (status != 0) 71 | err_abort(status, "Cancel thread"); 72 | 73 | status = pthread_join(thread_id[count], &result); 74 | if (status != 0) 75 | err_abort(status, "Join thread"); 76 | 77 | if (result == PTHREAD_CANCELED) 78 | printf("Thread %d canceled\n", count); 79 | else 80 | printf("Thread %d was not canceled\n", count); 81 | } 82 | } -------------------------------------------------------------------------------- /src/chapter05/cancel_subcontract.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define THREADS 5 5 | 6 | typedef struct team_tag { 7 | int join_i; 8 | pthread_t workers[THREADS]; 9 | } team_t; 10 | 11 | void *worker_routine(void *arg) 12 | { 13 | int counter; 14 | 15 | for(counter = 0; ; counter++) 16 | if ((counter % 1000) == 0) 17 | pthread_testcancel(); 18 | } 19 | 20 | void cleanup(void *arg) 21 | { 22 | team_t *team = (team_t *)arg; 23 | int count, status; 24 | 25 | for (count = team->join_i; count < THREADS; count++) { 26 | status = pthread_cancel(team->workers[count]); 27 | if (status != 0) 28 | err_abort(status, "Cancel worker"); 29 | 30 | status = pthread_detach(team->workers[count]); 31 | if (status != 0) 32 | err_abort(status, "Detach worker"); 33 | 34 | printf("Cleanup: canceled %d\n", count); 35 | } 36 | } 37 | 38 | void *thread_routine(void *arg) 39 | { 40 | team_t team; 41 | int count; 42 | void *result; 43 | int status; 44 | 45 | for (count = 0; count < THREADS; count++) { 46 | status = pthread_create(&team.workers[count], NULL, worker_routine, NULL); 47 | if (status != 0) 48 | err_abort(status, "Create worker"); 49 | } 50 | 51 | pthread_cleanup_push(cleanup, (void *)&team); 52 | 53 | for (team.join_i = 0; team.join_i < THREADS; team.join_i++) { 54 | status = pthread_join(team.workers[team.join_i], &result); 55 | if (status != 0) 56 | err_abort(status, "Join worker"); 57 | } 58 | 59 | pthread_cleanup_pop(1); 60 | return NULL; 61 | } 62 | 63 | int main() 64 | { 65 | pthread_t thread_id; 66 | int status; 67 | 68 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 69 | if (status != 0) 70 | err_abort(status, "Create team"); 71 | 72 | sleep(5); 73 | 74 | status = pthread_cancel(thread_id); 75 | if (status != 0) 76 | err_abort(status, "Cancel team"); 77 | 78 | status = pthread_join(thread_id, NULL); 79 | if (status != 0) 80 | err_abort(status, "Join team"); 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /src/chapter07/barrier.c: -------------------------------------------------------------------------------- 1 | #include "errors.h" 2 | #include "barrier.h" 3 | 4 | int barrier_init(barrier_t *barrier, int count) 5 | { 6 | int status; 7 | 8 | barrier->threshold = barrier->counter = count; 9 | barrier->cycle = 0; 10 | 11 | status = pthread_mutex_init(&barrier->mutex, NULL); 12 | if (status != 0) 13 | return status; 14 | 15 | status = pthread_cond_init(&barrier->cv, NULL); 16 | if (status != 0) 17 | return status; 18 | 19 | barrier->valid = BARRIER_VALID; 20 | return 0; 21 | } 22 | 23 | int barrier_destroy(barrier_t *barrier) 24 | { 25 | int status, status2; 26 | 27 | if (barrier->valid == BARRIER_VALID) 28 | return EINVAL; 29 | 30 | status = pthread_mutex_lock(&barrier->mutex); 31 | if (status != 0) 32 | return status; 33 | 34 | if (barrier->counter != barrier->threshold) { 35 | pthread_mutex_unlock(&barrier->mutex); 36 | return EBUSY; 37 | } 38 | 39 | barrier->valid = 0; 40 | status = pthread_mutex_unlock(&barrier->mutex); 41 | if (status != 0) 42 | return status; 43 | 44 | status = pthread_mutex_destroy(&barrier->mutex); 45 | status2 = pthread_cond_destroy(&barrier->cv); 46 | return (status != 0 ? status : status2); 47 | } 48 | 49 | int barrier_wait(barrier_t *barrier) 50 | { 51 | int status, cancel, tmp, cycle; 52 | 53 | if (barrier->valid != BARRIER_VALID) 54 | return EINVAL; 55 | 56 | status = pthread_mutex_lock(&barrier->mutex); 57 | if (status != 0) 58 | return status; 59 | 60 | cycle = barrier->cycle; 61 | 62 | if (--barrier->counter == 0) { 63 | barrier->cycle++; 64 | barrier->counter = barrier->threshold; 65 | 66 | status = pthread_cond_broadcast(&barrier->cv); 67 | if (status == 0) 68 | status = -1; 69 | } else { 70 | pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel); 71 | 72 | while (cycle == barrier->cycle) { 73 | status = pthread_cond_wait(&barrier->cv, &barrier->mutex); 74 | if (status != 0) 75 | break; 76 | } 77 | 78 | pthread_setcancelstate(cancel, &tmp); 79 | } 80 | 81 | pthread_mutex_unlock(&barrier->mutex); 82 | return status; 83 | } -------------------------------------------------------------------------------- /src/chapter03/trylock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define SPIN 10000000 5 | 6 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 7 | long counter; 8 | time_t end_time; 9 | 10 | void *counter_thread(void *arg) 11 | { 12 | int status; 13 | int spin; 14 | 15 | while (time(NULL) < end_time) 16 | { 17 | status = pthread_mutex_lock(&mutex); 18 | if (status != 0) 19 | err_abort(status, "Lock mutex"); 20 | 21 | for (spin = 0; spin < SPIN; spin++) 22 | counter++; 23 | 24 | status = pthread_mutex_unlock(&mutex); 25 | if (status != 0) 26 | err_abort(status, "Unlock mutex"); 27 | 28 | sleep(1); 29 | } 30 | 31 | printf("Counter is %ld\n", counter); 32 | return NULL; 33 | } 34 | 35 | void *monitor_thread(void *arg) 36 | { 37 | int status; 38 | int misses = 0; 39 | 40 | while (time(NULL) < end_time) 41 | { 42 | sleep(3); 43 | status = pthread_mutex_trylock(&mutex); 44 | if (status != EBUSY) 45 | { 46 | if (status != 0) 47 | err_abort(status, "Trylock mutex"); 48 | 49 | printf("Counter is %ld\n", counter/SPIN); 50 | 51 | status = pthread_mutex_unlock(&mutex); 52 | if (status != 0) 53 | err_abort(status, "Unlock mutex"); 54 | } else 55 | misses++; 56 | } 57 | 58 | printf("Monitor thread missed update %d times.\n", misses); 59 | return NULL; 60 | } 61 | 62 | int main() 63 | { 64 | int status; 65 | pthread_t counter_thread_id; 66 | pthread_t monitor_thread_id; 67 | 68 | end_time = time(NULL) + 60; 69 | 70 | status = pthread_create(&counter_thread_id, NULL, counter_thread, NULL); 71 | if (status != 0) 72 | err_abort(status, "Create counter thread"); 73 | 74 | status = pthread_create(&monitor_thread_id, NULL, monitor_thread, NULL); 75 | if (status != 0) 76 | err_abort(status, "Create monitor thread"); 77 | 78 | status = pthread_join(counter_thread_id, NULL); 79 | if (status != 0) 80 | err_abort(status, "Join counter thread"); 81 | 82 | status = pthread_join(monitor_thread_id, NULL); 83 | if (status != 0) 84 | err_abort(status, "Join monitor thread"); 85 | 86 | return 0; 87 | } -------------------------------------------------------------------------------- /src/chapter07/pthread_semaphore.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "errors.h" 4 | 5 | #define BUFF_SIZE 10 6 | #define PRODUCTER_SIZE 5 7 | #define CONSUMER_SIZE 5 8 | #define ITERATIONS 10 9 | 10 | typedef struct buff_type { 11 | int buff[BUFF_SIZE]; 12 | int in; 13 | int out; 14 | sem_t full; 15 | sem_t empty; 16 | sem_t mutex; 17 | } buff_t; 18 | 19 | buff_t shared; 20 | 21 | void *producter(void *arg) 22 | { 23 | int id = *(int*)arg; 24 | int item; 25 | int count; 26 | 27 | for (count = 0; count < ITERATIONS; count++) { 28 | sem_wait(&shared.empty); 29 | sem_wait(&shared.mutex); 30 | 31 | item = count; 32 | shared.buff[shared.in] = item; 33 | shared.in++; 34 | shared.in %= BUFF_SIZE; 35 | 36 | printf("[%d] Producing %d ...\n", id, item); 37 | fflush(stdout); 38 | 39 | sem_post(&shared.mutex); 40 | sem_post(&shared.full); 41 | 42 | if (count % 2 == 1) 43 | sleep(1); 44 | } 45 | } 46 | 47 | void *consumer(void *arg) 48 | { 49 | int id = *(int *)arg; 50 | int item; 51 | int count; 52 | 53 | for (count = 0; count < ITERATIONS; count++) { 54 | sem_wait(&shared.full); 55 | sem_wait(&shared.mutex); 56 | 57 | item = shared.buff[shared.out]; 58 | shared.out++; 59 | shared.out %= BUFF_SIZE; 60 | 61 | printf("[%d] Consuming %d ...\n", id, item); 62 | fflush(stdout); 63 | 64 | sem_post(&shared.mutex); 65 | sem_post(&shared.empty); 66 | 67 | if (count % 2 == 1) 68 | sleep(1); 69 | } 70 | } 71 | 72 | int main() 73 | { 74 | pthread_t producter_id, consumer_id; 75 | int count; 76 | int status; 77 | 78 | status = sem_init(&shared.full, 0, 0); 79 | if (status != 0) 80 | err_abort(status, "Sem init"); 81 | 82 | status = sem_init(&shared.empty, 0, BUFF_SIZE); 83 | if (status != 0) 84 | err_abort(status, "Sem init"); 85 | 86 | status = sem_init(&shared.mutex, 0, BUFF_SIZE); 87 | if (status != 0) 88 | err_abort(status, "Sem init"); 89 | 90 | for (count = 0; count < PRODUCTER_SIZE; count++){ 91 | pthread_create(&producter_id, NULL, producter, (void *)&count); 92 | } 93 | 94 | for (count = 0; count < CONSUMER_SIZE; count++) { 95 | pthread_create(&consumer_id, NULL, consumer, (void *)&count); 96 | } 97 | 98 | pthread_exit(NULL); 99 | } -------------------------------------------------------------------------------- /src/chapter06/atfork.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "errors.h" 5 | 6 | pid_t self_pid; 7 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 8 | 9 | void fork_prepare(void) 10 | { 11 | int status; 12 | 13 | status = pthread_mutex_lock(&mutex); 14 | if (status != 0) 15 | err_abort(status, "Lock in prepare handler"); 16 | } 17 | 18 | void fork_parent(void) 19 | { 20 | int status; 21 | 22 | status = pthread_mutex_unlock(&mutex); 23 | if (status != 0) 24 | err_abort(status, "Unlock in parent handler"); 25 | } 26 | 27 | void fork_child(void) 28 | { 29 | int status; 30 | 31 | self_pid = getpid(); 32 | status = pthread_mutex_unlock(&mutex); 33 | if (status != 0) 34 | err_abort(status, "Unlock in child handler"); 35 | } 36 | 37 | void *thread_routine(void *arg) 38 | { 39 | pid_t child_pid; 40 | int status; 41 | 42 | child_pid = fork(); 43 | if (child_pid == (pid_t)-1) 44 | errno_abort("Fork"); 45 | 46 | status = pthread_mutex_lock(&mutex); 47 | if (status != 0) 48 | err_abort(status, "Lock in child"); 49 | 50 | status = pthread_mutex_unlock(&mutex); 51 | if (status != 0) 52 | err_abort(status, "Unlock in child"); 53 | 54 | printf("After fork: %d (%d)\n", child_pid, self_pid); 55 | 56 | if (child_pid != 0) { 57 | if ((pid_t)-1 == waitpid(child_pid, (int *)0, 0)) 58 | errno_abort("Wait for child"); 59 | } 60 | return NULL; 61 | } 62 | 63 | int main(int argc, char *argv[]) 64 | { 65 | pthread_t fork_thread; 66 | int atfork_flag = 1; 67 | int status; 68 | 69 | if (argc > 1) 70 | atfork_flag = atoi(argv[1]); 71 | 72 | if (atfork_flag) { 73 | status = pthread_atfork(fork_prepare, fork_parent, fork_child); 74 | if (status != 0) 75 | err_abort(status, "Register fork handlers"); 76 | } 77 | 78 | self_pid = getpid(); 79 | status = pthread_mutex_lock(&mutex); 80 | if (status != 0) 81 | err_abort(status, "Lock mutex"); 82 | 83 | status = pthread_create(&fork_thread, NULL, thread_routine, NULL); 84 | if (status != 0) 85 | err_abort(status, "Create thread"); 86 | 87 | sleep(5); 88 | 89 | status = pthread_mutex_unlock(&mutex); 90 | if (status != 0) 91 | err_abort(status, "Unlock mutex"); 92 | 93 | status = pthread_join(fork_thread, NULL); 94 | if (status != 0) 95 | err_abort(status, "Join thread"); 96 | 97 | return 0; 98 | } -------------------------------------------------------------------------------- /src/chapter07/barrier_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "barrier.h" 3 | #include "errors.h" 4 | 5 | #define THREADS 5 6 | #define ARRAY 6 7 | #define INLOOPS 1000 8 | #define OUTLOOPS 10 9 | 10 | typedef struct thread_tag { 11 | pthread_t thread_id; 12 | int number; 13 | int increment; 14 | int array[ARRAY]; 15 | } thread_t; 16 | 17 | barrier_t barrier; 18 | thread_t threads[THREADS]; 19 | 20 | void *thread_routine(void *arg) 21 | { 22 | thread_t *self = (thread_t*)arg; 23 | int in_loop, out_loop, count, status; 24 | 25 | for (out_loop = 0; out_loop < OUTLOOPS; out_loop++) { 26 | status = barrier_wait(&barrier); 27 | if (status > 0) 28 | err_abort(status, "Wait on barrier"); 29 | 30 | for (in_loop = 0; in_loop < INLOOPS; in_loop++) 31 | for (count = 0; count < ARRAY; count++) 32 | self->array[count] += self->increment; 33 | 34 | status = barrier_wait(&barrier); 35 | if (status > 0) 36 | err_abort(status, "Wait on barrier"); 37 | 38 | if (status == -1) { 39 | int thread_num; 40 | 41 | for (thread_num = 0; thread_num < THREADS; thread_num++) 42 | threads[thread_num].increment += 1; 43 | } 44 | } 45 | 46 | return NULL; 47 | } 48 | 49 | int main() 50 | { 51 | int thread_count, array_count; 52 | int status; 53 | 54 | barrier_init(&barrier, THREADS); 55 | 56 | for (thread_count = 0; thread_count < THREADS; thread_count++) { 57 | threads[thread_count].increment = thread_count; 58 | threads[thread_count].number = thread_count; 59 | 60 | for (array_count = 0; array_count < ARRAY; array_count++) 61 | threads[thread_count].array[array_count] = array_count + 1; 62 | 63 | status = pthread_create(&threads[thread_count].thread_id, NULL, thread_routine, (void*)&threads[thread_count]); 64 | if (status != 0) 65 | err_abort(status, "Create threads"); 66 | } 67 | 68 | for (thread_count = 0; thread_count < THREADS; thread_count++) { 69 | status = pthread_join(threads[thread_count].thread_id, NULL); 70 | if (status != 0) 71 | err_abort(status, "Join threads"); 72 | 73 | printf("%02d: (%d)", thread_count, threads[thread_count].increment); 74 | 75 | for (array_count = 0; array_count < ARRAY; array_count++) 76 | printf("%010u ", threads[thread_count].array[array_count]); 77 | 78 | printf("\n"); 79 | } 80 | 81 | barrier_destroy(&barrier); 82 | return 0; 83 | } -------------------------------------------------------------------------------- /src/chapter07/pthread_barriers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define THREADS 5 5 | #define ARRAY 6 6 | #define INLOOPS 1000 7 | #define OUTLOOPS 10 8 | 9 | typedef struct thread_tag { 10 | pthread_t thread_id; 11 | int number; 12 | int increment; 13 | int array[ARRAY]; 14 | } thread_t; 15 | 16 | pthread_barrier_t barrier; 17 | thread_t threads[THREADS]; 18 | 19 | void *thread_routine(void *arg) 20 | { 21 | thread_t *self = (thread_t*)arg; 22 | int in_loop, out_loop, count, status; 23 | 24 | for (out_loop = 0; out_loop < OUTLOOPS; out_loop++) { 25 | status = pthread_barrier_wait(&barrier); 26 | if (status > 0) 27 | err_abort(status, "Wait on barrier"); 28 | 29 | for (in_loop = 0; in_loop < INLOOPS; in_loop++) 30 | for (count = 0; count < ARRAY; count++) 31 | self->array[count] += self->increment; 32 | 33 | status = pthread_barrier_wait(&barrier); 34 | if (status > 0) 35 | err_abort(status, "Wait on barrier"); 36 | 37 | if (status == -1) { 38 | int thread_num; 39 | 40 | for (thread_num = 0; thread_num < THREADS; thread_num++) 41 | threads[thread_num].increment += 1; 42 | } 43 | } 44 | 45 | return NULL; 46 | } 47 | 48 | int main() 49 | { 50 | int thread_count, array_count; 51 | int status; 52 | 53 | pthread_barrier_init(&barrier, NULL, THREADS); 54 | 55 | for (thread_count = 0; thread_count < THREADS; thread_count++) { 56 | threads[thread_count].increment = thread_count; 57 | threads[thread_count].number = thread_count; 58 | 59 | for (array_count = 0; array_count < ARRAY; array_count++) 60 | threads[thread_count].array[array_count] = array_count + 1; 61 | 62 | status = pthread_create(&threads[thread_count].thread_id, NULL, thread_routine, (void*)&threads[thread_count]); 63 | if (status != 0) 64 | err_abort(status, "Create threads"); 65 | } 66 | 67 | for (thread_count = 0; thread_count < THREADS; thread_count++) { 68 | status = pthread_join(threads[thread_count].thread_id, NULL); 69 | if (status != 0) 70 | err_abort(status, "Join threads"); 71 | 72 | printf("%02d: (%d)", thread_count, threads[thread_count].increment); 73 | 74 | for (array_count = 0; array_count < ARRAY; array_count++) 75 | printf("%010u ", threads[thread_count].array[array_count]); 76 | 77 | printf("\n"); 78 | } 79 | 80 | pthread_barrier_destroy(&barrier); 81 | return 0; 82 | } -------------------------------------------------------------------------------- /src/chapter05/tsd_destructor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | typedef struct private_tag { 5 | pthread_t thread_id; 6 | char *string; 7 | } private_t; 8 | 9 | pthread_key_t identity_key; 10 | pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER; 11 | long identity_key_counter = 0; 12 | 13 | void identity_key_destructor(void *value) 14 | { 15 | private_t *private = (private_t*)value; 16 | int status; 17 | 18 | printf("thread \"%s\" exiting...\n", private->string); 19 | free(value); 20 | 21 | status = pthread_mutex_lock(&identity_key_mutex); 22 | if (status != 0) 23 | err_abort(status, "Lock key mutex"); 24 | 25 | identity_key_counter--; 26 | if (identity_key_counter <= 0) { 27 | status = pthread_key_delete(identity_key); 28 | if (status != 0) 29 | err_abort(status, "Delete key"); 30 | printf("key delete...\n"); 31 | } 32 | 33 | status = pthread_mutex_unlock(&identity_key_mutex); 34 | if (status != 0) 35 | err_abort(status, "Unlock key mutex"); 36 | } 37 | 38 | void *identity_key_get(void) 39 | { 40 | void *value; 41 | int status; 42 | 43 | value = pthread_getspecific(identity_key); 44 | if (value == NULL) { 45 | value = malloc(sizeof(private_t)); 46 | if (value == NULL) 47 | errno_abort("Allocate key value"); 48 | 49 | status = pthread_setspecific(identity_key, value); 50 | if (status != 0) 51 | err_abort(status, "Set TSD"); 52 | } 53 | 54 | return value; 55 | } 56 | 57 | void *thread_routine(void *arg) 58 | { 59 | private_t *value; 60 | 61 | value = (private_t*) identity_key_get(); 62 | value->thread_id = pthread_self(); 63 | value->string = (char*)arg; 64 | printf("thread \"%s\" starting...\n", value->string); 65 | sleep(2); 66 | return NULL; 67 | } 68 | 69 | int main() 70 | { 71 | pthread_t thread_1, thread_2; 72 | private_t *value; 73 | int status; 74 | 75 | status = pthread_key_create(&identity_key, identity_key_destructor); 76 | if (status != 0) 77 | err_abort(status, "Create key"); 78 | 79 | identity_key_counter = 3; 80 | 81 | value = (private_t*)identity_key_get(); 82 | value->thread_id = pthread_self(); 83 | value->string = "Main thread"; 84 | 85 | status = pthread_create(&thread_1, NULL, thread_routine, "thread 1"); 86 | if (status != 0) 87 | err_abort(status, "Create thread 1"); 88 | 89 | status = pthread_create(&thread_2, NULL, thread_routine, "thread 2"); 90 | if (status != 0) 91 | err_abort(status, "Create thread 2"); 92 | 93 | pthread_exit(NULL); 94 | } -------------------------------------------------------------------------------- /src/chapter05/sched_attr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "errors.h" 5 | 6 | void *thread_routine(void *arg) 7 | { 8 | int my_policy; 9 | struct sched_param my_param; 10 | int status; 11 | 12 | status = pthread_getschedparam(pthread_self(), &my_policy, &my_param); 13 | if (status != 0) 14 | err_abort(status, "Get sched"); 15 | 16 | printf("thread_routine running at %s/%d\n", 17 | (my_policy == SCHED_FIFO) ? "FIFO" 18 | : (my_policy == SCHED_RR ? "RR" 19 | : (my_policy == SCHED_OTHER ? "OTHER" 20 | : "unknown")), my_param.__sched_priority); 21 | 22 | return NULL; 23 | } 24 | 25 | int main() 26 | { 27 | pthread_t thread_id; 28 | pthread_attr_t thread_attr; 29 | int thread_policy; 30 | struct sched_param thread_param; 31 | int status, rr_min_priority, rr_max_priority; 32 | 33 | status = pthread_attr_init(&thread_attr); 34 | if (status != 0) 35 | err_abort(status, "Init attr"); 36 | 37 | status = pthread_attr_getschedpolicy(&thread_attr, &thread_policy); 38 | if (status != 0) 39 | err_abort(status, "Get sched policy"); 40 | 41 | status = pthread_attr_getschedparam(&thread_attr, &thread_param); 42 | if (status != 0) 43 | err_abort(status, "Get sched attr"); 44 | 45 | printf("Default policy is %s, priority is %d\n", 46 | (thread_policy == SCHED_FIFO) ? "FIFO" 47 | : (thread_policy == SCHED_RR ? "RR" 48 | : (thread_policy == SCHED_OTHER ? "OTHER" 49 | : "unknown")), thread_param.sched_priority); 50 | 51 | status = pthread_attr_setschedpolicy(&thread_attr, SCHED_RR); 52 | if (status != 0) 53 | printf("Unable to set SCHED_RR policy.\n"); 54 | else { 55 | rr_min_priority = sched_get_priority_min(SCHED_RR); 56 | if (rr_max_priority == -1) 57 | errno_abort("Get SCHED_RR min priority"); 58 | 59 | rr_max_priority = sched_get_priority_max(SCHED_RR); 60 | if (rr_max_priority == -1) 61 | errno_abort("Get SCHED_RR max priority"); 62 | 63 | thread_param.sched_priority = (rr_min_priority + rr_max_priority) / 2; 64 | 65 | printf("SCHED_RR priority range is %d to %d, using %d\n", 66 | rr_min_priority, rr_max_priority, thread_param.sched_priority); 67 | 68 | status = pthread_attr_setschedparam(&thread_attr, &thread_param); 69 | if (status != 0) 70 | err_abort(status, "Set params"); 71 | 72 | printf("Creating thread at RR/%d\n", thread_param.sched_priority); 73 | 74 | status = pthread_attr_setinheritsched(&thread_attr, PTHREAD_EXPLICIT_SCHED); 75 | if (status != 0) 76 | err_abort(status, "Set inherit"); 77 | } 78 | 79 | status = pthread_create(&thread_id, &thread_attr, thread_routine, NULL); 80 | if (status != 0) 81 | err_abort(status, "Create thread"); 82 | 83 | status = pthread_join(thread_id, NULL); 84 | if (status != 0) 85 | err_abort(status, "Join thread"); 86 | 87 | printf("Main exiting\n"); 88 | return 0; 89 | } -------------------------------------------------------------------------------- /doc/08_synchronization_essentials.md: -------------------------------------------------------------------------------- 1 | # 线程同步精要 2 | 3 | 本章节摘录自陈硕 《Linux多线程服务端编程:使用muduoC++网络库》 第二章关于线程同步笔记。 4 | 5 | ## 线程同步四项原则 6 | 7 | 线程同步的四项原则,按重要性排列: 8 | 9 | - 首要原则是尽量最低限度地共享对象,减少需要同步的场合。一个对象能不暴露给别的线程就不要暴露;如果要暴露,优先考虑 `immutable` 对象;实在不行才暴露可修改的对象,并用同步措施来充分保护它。 10 | - 其次是使用高级的并发编程构件,如 `TaskQueue`、`Producer-ConsumerQueue`、`CountDownLatch` 等等。 11 | - 最后不得已必须使用底层同步原语(primitives)时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量。 12 | - 除了使用 `atomic` 整数之外,不自己编写 `lock-free` 代码,也不要用“内核级”同步原语。不凭空猜测“哪种做法性能会更好”,比如`spin lock` vs `mutex`。 13 | 14 | ## 互斥器(mutex) 15 | 16 | 互斥器(mutex)是使用得最多的同步原语,粗略地说,它保护了临界区,任何一个时刻最多只能有一个线程在此 mutex 划出的临界区内活动。单独使用 mutex 时,我们主要为了保护共享数据。 17 | 18 | 主要原则: 19 | 20 | - 在C++中,应该使用用`RAII`手法封装`mutex`的创建、销毁、加锁、解锁这四个操作。Java里的 `synchronized` 语句和C#的 `using` 语句也有类似的效果,即保证锁的生效期间等于一个作用域(scope),不会因异常而忘记解锁。 21 | - 只用非递归的 `mutex`(即不可重入的mutex)。 22 | - 不手工调用 `lock()` 和 `unlock()` 函数, 23 | - 在每次构造 `Guard` 对象的时候,思考一路上(调用栈上)已经持有的锁,防止因加锁顺序不同而导致死锁(deadlock)。 24 | 25 | 次要原则: 26 | 27 | - 不使用跨进程的 `mutex`,进程间通信只用 TCPsockets。 28 | - 加锁、解锁在同一个线程,线程a不能去 `unlock` 线程b已经锁住的 `mutex`(RAII自动保证)。 别忘了解锁(RAII自动保证)。 29 | - 不重复解锁(RAII自动保证)。 30 | - 必要的时候可以考虑用 `PTHREAD_MUTEX_ERRORCHECK` 来排错。 31 | 32 | ## 条件变量 33 | 34 | 互斥器(mutex)是加锁原语,用来排他性地访问共享数据,它不是等待原语。在使用 mutex 的时候,我们一般都会期望加锁不要阻塞,总是能立刻拿到锁。然后尽快访问数据,用完之后尽快解锁,这样才能不影响并发性和性能。 35 | 36 | 如果需要等待某个条件成立,我们应该使用条件变量(conditionvariable)。条件变量顾名思义是一个或多个线程等待某个布尔表达式为真,即等待别的线程“唤醒”它。条件变量只有一种正确使用的方式,几乎不可能用错。 37 | 38 | 对于wait端: 39 | 40 | - 必须与 `mutex` 一起使用,该布尔表达式的读写需受此mutex保护。 41 | - 在 `mutex` 已上锁的时候才能调用 `wait()`。 42 | - 把判断布尔条件和 `wait()` 放到 `while` 循环中。 43 | 44 | 对于 signal/broadcast 端: 45 | 46 | - 不一定要在 `mutex` 已上锁的情况下调用 `signal`(理论上)。 47 | - 在 `signal` 之前一般要修改布尔表达式。 48 | - 修改布尔表达式通常要用 `mutex` 保护(至少用作fullmemorybarrier)。 49 | - 注意区分 `signal` 与 `broadcast`: `broadcast` 通常用于表明状态变化,`signal` 通常用于表示资源。 50 | 51 | 条件变量是非常底层的同步原语,很少直接使用,一般都是用它来实现高层的同步措施,如 `BlockingQueue` 或 `CountDownLatch`。 52 | 53 | 倒计时(CountDownLatch)是一种常用且易用的同步手段。它主要有两种用途: 54 | 55 | - 主线程发起多个子线程,等这些子线程各自都完成一定的任务之后,主线程才继续执行。通常用于主线程等待多个子线程完成初始化。 56 | - 主线程发起多个子线程,子线程都等待主线程,主线程完成其他一些任务之后通知所有子线程开始执行。通常用于多个子线程等待主线程发出“起跑”命令。 57 | 58 | ## 不要使用读写锁和信号量 59 | 60 | 读写锁(Readers-Writerlock,简写为rwlock)是个看上去很美的抽象,它明确区分了 read 和 write 两种行为。 61 | 62 | 初学者常干的一件事情是,一见到某个共享数据结构频繁读而很少写,就把 mutex 替换为 rwlock。甚至首选 rwlock 来保护共享状态,这不见得是正确的。 63 | 64 | - 从正确性方面来说,一种典型的易犯错误是在持有 readlock 的时候修改了共享数据。这通常发生在程序的维护阶段,为了新增功能,程序员不小心在原来 readlock 保护的函数中调用了会修改状态的函数。这种错误的后果跟无保护并发读写共享数据是一样的。 65 | - 从性能方面来说,读写锁不见得比普通 mutex 更高效。无论如何 readerlock 加锁的开销不会比 mutexlock 小,因为它要更新当前 reader 的数目。如果临界区很小,锁竞争不激烈,那么 mutex 往往会更快。 66 | - readerlock 可能允许提升为 writerlock,也可能不允许提升。如果处理不好容易导致程序崩溃和死锁。 67 | - 通常 readerlock 是可重入的,writerlock 是不可重入的。但是为了防止 writer 饥饿,writerlock通常会阻塞后来的readerlock,因此 readerlock 在重入的时候可能死锁。另外,在追求低延迟读取的场合也不适用读写锁。 68 | 69 | 对于信号量(Semaphore),陈硕认为信号量不是必备的同步原语,因为条件变量配合互斥器可以完全替代其功能,而且更不易用错。 70 | 信号量的另一个问题在于它有自己的计数值,而通常我们自己的数据结构也有长度值,这就造成了同样的信息存了两份,需要时刻保持一致,这增加了程序员的负担和出错的可能。 71 | 72 | ## 归纳与总结 73 | 74 | 作者认为,应该先把程序写正确(并尽量保持清晰和简单),然后再考虑性能优化,如果确实还有必要优化的话。这在多线程下仍然成立。让一个正确的程序变快,远比“让一个快的程序变正确”容易得多。 75 | 76 | “效率”并不是我的主要考虑点,我提倡正确加锁而不是自己编写 `lock-free`算法(使用原子整数除外),更不要想当然地自己发明同步设施。在没有实测数据支持的情况下,妄谈哪种做法效率更高是靠不住的,不能听信传言或凭感觉“优化”。很多人误认为用锁会让程序变慢,其实真正影响性能的不是锁,而是锁争用(lockcontention)。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POSIX 多线程程序设计 2 | 3 | 本项目所有内容摘录自《POSIX多线程程序设计》与《Linux多线程服务端编程:使用muduoC++网络库》部分章节,简化了书中的内容,并提供了完整的源代码实现。 4 | 5 | ## 编译、运行 6 | 7 | 前提,你需要安装 CMake。所有章节源代码都放在 `src` 目录下,你可以直接编译后运行。 8 | 9 | ```shell 10 | git clone https://github.com/Veinin/programming-with-POSIX-threads-tutorials.git 11 | cd programming-with-POSIX-threads-tutorials 12 | ./build.sh 13 | ``` 14 | 15 | 运行实例代码: 16 | 17 | ```shell 18 | $ ./bin/barrier_main 19 | 00: (10)0000045001 0000045002 0000045003 0000045004 0000045005 0000045006 20 | 01: (11)0000055001 0000055002 0000055003 0000055004 0000055005 0000055006 21 | 02: (12)0000065001 0000065002 0000065003 0000065004 0000065005 0000065006 22 | 03: (13)0000075001 0000075002 0000075003 0000075004 0000075005 0000075006 23 | 04: (14)0000085001 0000085002 0000085003 0000085004 0000085005 0000085006 24 | ``` 25 | 26 | ## 目录 27 | 28 | * [概述](./doc/01_overview.md) 29 | * [术语定义](./doc/01_overview.md#术语定义) 30 | * [线程的好处](./doc/01_overview.md#线程的好处) 31 | * [线程的代价](./doc/01_overview.md#线程的代价) 32 | * [选择线程还是不用线程](./doc/01_overview.md#选择线程还是不用线程) 33 | * [POSIX 线程概念](./doc/01_overview.md#POSIX线程概念) 34 | * [异步编程举例](./doc/01_overview.md#异步编程举例) 35 | 36 | * [线程](./doc/02_threads.md) 37 | * [建立和使用线程](./doc/02_threads.md#建立和使用线程) 38 | * [线程生命周期](./doc/02_threads.md#线程生命周期) 39 | 40 | * [同步](./doc/03_synchronization.md) 41 | * [不变量、临界区和谓词](./doc/03_synchronization.md#不变量、临界区和谓词) 42 | * [互斥量](./doc/03_synchronization.md#互斥量) 43 | * [条件变量](./doc/03_synchronization.md#条件变量) 44 | 45 | * [使用线程方式](./doc/04_a_few_ways_to_threads.md) 46 | * [流水线](./doc/04_a_few_ways_to_threads.md#流水线) 47 | * [工作组](./doc/04_a_few_ways_to_threads.md#工作组) 48 | * [客户/服务器](./doc/04_a_few_ways_to_threads.md#客户/服务器) 49 | 50 | * [线程高级编程](./doc/05_advanced_thread_programming.md) 51 | * [一次性初始化](./doc/05_advanced_thread_programming.md#一次性初始化) 52 | * [属性](./doc/05_advanced_thread_programming.md#属性) 53 | * [取消](./doc/05_advanced_thread_programming.md#取消) 54 | * [线程私有数据](./doc/05_advanced_thread_programming.md#线程私有数据) 55 | * [线程实时调度](./doc/05_advanced_thread_programming.md#线程实时调度) 56 | 57 | * [POSIX 针对线程的调整](./doc/06_posix_adjusts_to_threads.md) 58 | * [多线程与fork](./doc/06_posix_adjusts_to_threads.md#多线程与fork) 59 | * [多线程与exec](./doc/06_posix_adjusts_to_threads.md#多线程与exec) 60 | * [多线程与signal](./doc/06_posix_adjusts_to_threads.md#多线程与signal) 61 | * [多线程与stdio](./doc/06_posix_adjusts_to_threads.md#多线程与stdio) 62 | * [进程结束](./doc/06_posix_adjusts_to_threads.md#进程结束) 63 | * [线程安全函数](./doc/06_posix_adjusts_to_threads.md#线程安全函数) 64 | 65 | * [线程扩展](./doc/07_extended.md) 66 | * [栅栏(Barriers)](./doc/07_extended.md#%E6%A0%85%E6%A0%8Fbarriers) 67 | * [读/写锁(Read-Write Lock)](./doc/07_extended.md#%E8%AF%BB%E5%86%99%E9%94%81read-write-lock) 68 | * [自旋锁(Spin Locks)](./doc/07_extended.md#%E8%87%AA%E6%97%8B%E9%94%81spin-locks) 69 | * [信号量(Semaphore)](./doc/07_extended.md##%E4%BF%A1%E5%8F%B7%E9%87%8Fsemaphore) 70 | * [工作队列](./doc/07_extended.md#工作队列) 71 | 72 | * [线程同步精要](./doc/08_synchronization_essentials.md) 73 | * [线程同步四项原则](./doc/08_synchronization_essentials.md#线程同步四项原则) 74 | * [互斥器](./doc/08_synchronization_essentials.md#%E4%BA%92%E6%96%A5%E5%99%A8mutex) 75 | * [条件变量](./doc/08_synchronization_essentials.md#条件变量) 76 | * [不要使用读写锁和信号量](./doc/08_synchronization_essentials.md#不要使用读写锁和信号量) -------------------------------------------------------------------------------- /doc/06_posix_adjusts_to_threads.md: -------------------------------------------------------------------------------- 1 | # POSIX 针对线程的调整 2 | 3 | ## 多线程与fork 4 | 5 | 多线程进程调用 `fork` 创造的子进程,只有调用 `fork` 的线程在子进程内存在,除了当前调用的线程,其他线程在子进程中都会消失,但消失的线程状态仍然保留为调用 `fork` 时的相同状态,线程会拥有与在父进程内当前调用线程的相同状态,拥有相同的互斥量,同样的线程私有数据键值等。 6 | 7 | 在 `forked` 的进程中只有一个线程,消失的其他线程并不会调用诸如 `pthread_exit` 退出,线程也不会再运用线程私有数据 `destructors` 或清除处理函数。 8 | 这里就存在一个很危险的局面,其他线程可能正好位于临界区之内,持有了某个锁,而它突然死亡,也就再也没有机会去解锁了。而如果子进程试图去对某个 `mutex` 加锁,那么马上就会造成死锁的局面。 9 | 10 | 综上所述,除非你打算很快地在子进程中调用 `exec` 执行一个新程序, 否则应避免在一个多线程的程序中使用 `fork`。 11 | 12 | 如果你不能避免,那么你应该注意在子进程中不能调用以下这些函数(参考《Linux多线程服务端编程》): 13 | 14 | - `malloc`,会访问全局状态,肯定会加锁。 15 | - 任何分配和释放内存的函数,诸如`new`、`delete`...... 16 | - 任何 Pthreads 函数。 17 | - `printf` 系列函数。其他线程可能持有了stdout/stderr的锁。 18 | - `sigle` 中除了“sigle安全”之外的任何函数,应避免在信号处理函数内使用 `fork`。 19 | 20 | 另外,Pthreads 增加了 `pthread_atfork` “fork 处理器” 机制以允许你的代码越过 `fork` 调用保护数据和不变量。 21 | 22 | ```c 23 | int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)); 24 | ``` 25 | 26 | 该函数中,`prepare fork` 处理器以正确的顺序锁住所有的由相关代码使用的互斥量以阻止死锁的发生。`parent fork` 处理器只需要开锁所有互斥量即可,以允许父进程和所有线程继续正常工作。`child fork` 处理器经常可以与 parent fork 处评器一样; 但是有时需要重置程序或库的状态。 27 | 28 | 在程序 `atfork.c` 中展示了如何使用 `fork` 处理器。 29 | 30 | ## 多线程与exec 31 | 32 | `exec` 函数功能是消除当前程序的环境并且用一个新程序代替它,所以并没有受线程的影响。 33 | 34 | ## 多线程与signal 35 | 36 | 在多线程程序中,使用 signal 的第一原则就是 **不要使用 signal**。 37 | 38 | 尽管修改进程信号行为本身是线程安全的,但是不能防止其他线程随后很快地设置一个新的信号行为。任何印象线程的信号同样也会影响整个进程,这意味着向进程或进程内的任何线程传送一个 `SIGKILL` 信号,将终止进程。传送一个 `SIGSTOP` 停止命令时,将导致所有的线程停止直到收到 `SIGCOUNT` 信号。 39 | 40 | ## 多线程与stdio 41 | 42 | Pthreads 要求 ANSI C 标准 I/O (stdio) 函数是线程安全的。因为 stdio 包需要为输出缓冲区和文件状态指定静态存储区,stdio 实现将使用互斥量或信号灯等同步机制。 43 | 44 | ### flockfile 和 funlockfile 45 | 46 | 在一些情况里,一系列 stdio 操作以不被中断的顺序执行是重要的。例如,一个提示符后面跟着一个从终端的读操作。为了不让其他线程在两个操作之间读 stdin 或者写 47 | stdout,你应该在两个调用前后锁住 stdin 和 stdou。你可以使用使用 `flockfile` 和 `funlockfile` 以及 `ftrylockfile` 函数来确保一系列写操作不会被从其他线程的文件存取打断。 48 | 49 | ```c 50 | void flock file(FILE *file); 51 | int ftrylockfile(FILE *file) 52 | void funlockfile(FILE *file); 53 | ``` 54 | 55 | 你可以参考程序 `flock.c` 实例。 56 | 57 | ### getchar_unlocked 和 putchar_unlocked 58 | 59 | 函数 `getchar` 和 `putchar` 分别操作 stdin 和 stdout,而 `getc` 和 `putc` 能在任何stdio 文件流上被使用。Pthreads 要求这些函数锁住 stdio 流数据来防止代码对 stdio 缓冲区的偶然破坏。 60 | 61 | Phtread 提供了函数 `getc_unlocked`、`putc_unlocked`、 `getchar_unlocked` 和 `putchar_unlocked`,但它们不执行任何锁操作,因此你必须在这些操作附近使用 `flockfile` 和 `fimlockfili`。 62 | 63 | ```c 64 | int getc_unlocked(FILE *stream); 65 | int getchar_unlocked(void); 66 | int putc_unlocked(int c, FILE *stream); 67 | int putchar_unlocked (int c); 68 | ``` 69 | 70 | 程序 `putchar.c` 显示了使用 `putchar` 和在一个文件锁内调用一系列 `putchar_unlocked`之间的差异。 71 | 72 | ## 进程结束 73 | 74 | 在一个多线程程序中,主函数是”进程主线程的启动函数”,从主函数返回将终止整个进程。与进程相关的所有内存(和线程)将消失。线程也不会执行清除处理器或线程私有数据 `destructors` 函数。调用 `exit` 具有同样的效果,你可以调用 `exit` 来很快地停止所有的线程。 75 | 76 | 当你不想使用起始线程或让它等待其他线程结束时,可以通过调用 `pthread_exit` 而非返回或调用 `exit` 退出主函数。从主函数中调用 `pthread_exit` 将在不影响进程内其他线程的前提下终止起始线程,允许其他线程继续运作,直到正常完成。 77 | 78 | ## 线程安全函数 79 | 80 | Pthreads 定义了现存函数的线程安全的变体,它们在相应函数名结尾处添加后缀 `_r`: 81 | 82 | - 用户和终端ID,`getlogin_r`、`ctermid`、`ttyname_r`。 83 | - 目录搜索,`readdir_r`。 84 | - 字符串 token,`strtok_r`。 85 | - 时间表示,`asctime_r`、`ctime_r`、`gmtime_r`、`localtime_r`。 86 | - 随机数产生,`read_r`。 87 | - 组和用户数据库,`getgrgid_r`、`getgrnam_r`、`getpwuid_r`、`getpwnam_r`。 88 | 89 | 程序 `getlogin.c` 是一个如何调用获取用户和终端ID的实例。 -------------------------------------------------------------------------------- /doc/02_threads.md: -------------------------------------------------------------------------------- 1 | # 线程 2 | 3 | ## 建立和使用线程 4 | 5 | ### 线程标识符 6 | 7 | 程序中使用线程标识符 ID 来表示线程。线程 ID 属于封装的 pthreadLt 类型。 8 | 为建立线程,你需要在程序中声明一个 `pthread_t` 类型的变量。 如果只需在某个函数中使用线程 ID,或者函数直到线程终止时才返回,则可以将线程 ID 声明为自动存储变量,不过大部分时间内, 线程 ID 保存在共享变量中(静态或外部), 或者保存在堆空间的结构体中。 9 | 10 | ```c 11 | pthread_t thread; 12 | ``` 13 | 14 | ### 创建线程 15 | 16 | 通过向 `pthread_create` 函数传送线程函数地址和线程函数调用的参数来创建线程。线程函数应该只有一个 `void *` 类型参数,并返回相同的类型值。 17 | 当创建线程时,`pthread_create` 函数返回一个 `pthread_t` 类型的线程 ID, 并保存在 thread 参数中。 通过这个线程 ID, 程序可以引用该线程。 18 | 19 | ```c 20 | int pthread_create(pthread_t *thread, const pthreae_attr_t *attr, void *(*start)(void *), void *arg); 21 | ``` 22 | 23 | ### 获得自己的线程ID 24 | 25 | 线程可以通过调用 `pthread_self` 来获得自身的 ID。除非线程的创建者或者线程本身将线程 ID 保存于某处,否则不可能获得一个线程的 ID。要对线程进行任何操作都必须通过线程 ID。 26 | 27 | ```c 28 | pthread_t pthread_self(void); 29 | ``` 30 | 31 | ### 比较线程 32 | 33 | 可以使用 `pthread_equal` 函数来比较两个线程 ID,只能比较二者是否相同。比较两个线程 ID 谁大谁小是没有任何意义的,因为线程 ID 之间不存在顺序。如果两个线程 ID 表示同一个线程,则 `pthread_equal` 函数返回非零值,否则返回零值。 34 | 35 | ```c 36 | int pthread_equal(pthread_t tl, pthread_t t2); // 相等返回非0值 37 | ``` 38 | 39 | ### 分离线程 40 | 41 | 如果要创建一个从不需要控制的线程,可以是用属性(attribute)来建立线程以使它可分离的。如果不想等待创建的某个线程,而且知道不再需要控制它,可以使用 `pthread_detach` 函数来分离它。 42 | 分离一个正在运行的线程不会对线程带来任何影响,仅仅是通知系统当该线程结束时,其所属资源可以被回收。 43 | 44 | ```c 45 | int pthread_detach(pthread_t thread); 46 | ``` 47 | 48 | ### 退出线程 49 | 50 | 当 C 程序运行时,首先运行 `main` 函数。在线程代码中, 这个特殊的执行流被称为 “**初始线程**” 或 “**主线程**”。 你可以在初始线程中做任何你能在普通线程中做的事情。也可以调用 `pthread_exit` 来终止自己。 51 | 52 | ```c 53 | int pthread_exit(void *value_ptr); 54 | ``` 55 | 56 | ### 取消线程 57 | 58 | 外部发送终止信号给指定线程,如果成功则返回0,否则返回非0。发送成功并不意味着线程会终止。 59 | 另外,如果一个线程被回收,终止线程的 ID 可能被分配给其他新的线程,使用该 ID 调用 `pthread_cancel` 可能就会取消一个不同的线程, 而不是返回 ESRCH 错误。 60 | 61 | ```c 62 | int pthread_cancel(pthread_t thread); 63 | ``` 64 | 65 | ### 等待线程结束 66 | 67 | 如果需要获取线程的返回值,或者需要获知其何时结束,应该调用 `pthread_join` 函数。 `pthread_join` 函数将阻塞其调用者直到指定线程终止。然后,可以选择地保存线程的返回值。调 68 | 用 `pthread_join` 函数将自动分离指定的线程。线程会在返回时被回收,回收将释放所有在线程终止时未释放的系统和进程资源,包栝保存线程返回值的内存空间、堆栈、保存寄存器状态的内存空间等。所以,在线程终止后上述资源就不该被访问了。 69 | 70 | ```c 71 | int pthread_join(pthread_t thread, void **value_ptr); 72 | ``` 73 | 74 | ## 线程生命周期 75 | 76 | 线程有四种基本状态: 77 | 78 | - 就绪(Ready)状态。线程能够运行,但在等待可用的处理器,可能刚刚启动,或刚刚从阻塞中恢复,或者被其他线程抢占。 79 | - 运行(Running)状态。线程正在运行,在多处器系统中,可能有多个线程处于运行态线程由于等待处理器外的其他条件无法运行,如条件变量的改变、加锁互斥量或 I/O 操作结束。 80 | - 阻塞(Blocked)状态。线程由于等待处理器外的其他条件无法运行,如条件变量的改变、加锁互斥量或 I/O 操作结束。 81 | - 终止(Terminated)状态。线程从起始函数中返回,或调用 pthread_exit,或者被取消,终止自己并完成所有资源清理。不是被分离,也不是被连接,一且线程被分离或者连接,它就可以被收回。 82 | 83 | 下面是线程的状态转换图: 84 | 85 | ![pipeline](images/lifecycle.png) 86 | 87 | 下面程序展示了一个线程使用的完整生命周期实例: 88 | 89 | ```c 90 | #include 91 | #include "errors.h" 92 | 93 | void *thread_routine(void *arg) { 94 | return arg; 95 | } 96 | 97 | int main(void) { 98 | pthread_t thread_id; 99 | void *thread_result; 100 | int status; 101 | 102 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 103 | if (status != 0) 104 | err_abort(status, "Create thread"); 105 | 106 | status = pthread_join(thread_id, &thread_result); 107 | if (status != 0) 108 | err_abort(status, "Join thread"); 109 | 110 | if (thread_result == NULL) 111 | return 0; 112 | else 113 | return 1; 114 | } 115 | ``` 116 | 117 | 上面程序中 `pthread_create` 创建线程后,线程处于就绪状态。受调度机制的限制,新线程可能在就绪状态下停留一段时间才被执行。 118 | 当处理器选中一个就绪线程执行它时,该线程进入运行态。通常这意味着某个其他线程被阻塞或者被时间片机制抢占,处理器会保存被阻塞(或抢占)线程的环境并恢复下二个就绪线程的环境。 119 | 主线程在调用 `pthread_join` 进入阻塞状态,等待它创建的线程运行结束。 120 | 当调用 `pthread_exit` 退出线程或调用 `pthread_cancel` 取消线程时, 线程在调用完清理过程后也将进入终止态。而主线程等到创建的线程终止后重新运行直到结束。 -------------------------------------------------------------------------------- /src/chapter07/workq_main.c: -------------------------------------------------------------------------------- 1 | #include "workq.h" 2 | #include "errors.h" 3 | 4 | #define ITERATIONS 25 5 | 6 | typedef struct power_tag { 7 | int value; 8 | int power; 9 | } power_t; 10 | 11 | typedef struct engine_tag { 12 | struct engine_tag *link; 13 | pthread_t thread_id; 14 | int calls; 15 | } engine_t; 16 | 17 | pthread_key_t engine_key; 18 | pthread_mutex_t engin_list_mutex = PTHREAD_MUTEX_INITIALIZER; 19 | engine_t *engin_list_head = NULL; 20 | workq_t workq; 21 | 22 | void destructor(void *value_ptr) 23 | { 24 | engine_t *engine = (engine_t*)value_ptr; 25 | 26 | pthread_mutex_lock(&engin_list_mutex); 27 | engine->link = engin_list_head; 28 | engin_list_head = engine; 29 | pthread_mutex_unlock(&engin_list_mutex); 30 | } 31 | 32 | void engine_routine(void *arg) 33 | { 34 | engine_t *engine; 35 | power_t *power = (power_t*)arg; 36 | int result, count; 37 | int status; 38 | 39 | engine = pthread_getspecific(engine_key); 40 | if (engine == NULL) { 41 | engine = (engine_t*)malloc(sizeof(engine_t)); 42 | 43 | status = pthread_setspecific(engine_key, (void*)engine); 44 | if (status != 0) 45 | err_abort(status, "Set tsd"); 46 | 47 | engine->thread_id = pthread_self(); 48 | engine->calls = 1; 49 | } else 50 | engine->calls++; 51 | 52 | result = 1; 53 | printf("Engine: computing %d^%d\n", power->value, power->power); 54 | 55 | for (count = 1; count < power->power; count++) 56 | result *= power->value; 57 | 58 | free(arg); 59 | } 60 | 61 | void *thread_routine(void *arg) 62 | { 63 | power_t *element; 64 | int count; 65 | unsigned int seed = (unsigned int) time(NULL); 66 | int status; 67 | 68 | for (count = 0; count < ITERATIONS; count++) { 69 | element = (power_t*)malloc(sizeof(power_t)); 70 | if (element == NULL) 71 | err_abort(status, "Allocate element"); 72 | 73 | element->value = rand_r(&seed) % 20; 74 | element->power = rand_r(&seed) % 7; 75 | 76 | printf("Request: %d^%d\n", element->value, element->power); 77 | 78 | status = workq_add(&workq, (void*)element); 79 | if (status != 0) 80 | err_abort(status, "Add to work queue"); 81 | 82 | sleep(rand_r(&seed) % 5); 83 | } 84 | return NULL; 85 | } 86 | 87 | int main() 88 | { 89 | pthread_t thread_id; 90 | engine_t *engine; 91 | int count = 0, calls = 0; 92 | int status; 93 | 94 | status = pthread_key_create(&engine_key, destructor); 95 | if (status != 0) 96 | err_abort(status, "Create key"); 97 | 98 | status = workq_init(&workq, 4, engine_routine); 99 | if (status != 0) 100 | err_abort(status, "Init work queue"); 101 | 102 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 103 | if (status != 0) 104 | err_abort(status, "Create thread"); 105 | 106 | status = pthread_join(thread_id, NULL); 107 | if (status != 0) 108 | err_abort(status, "Join thread"); 109 | 110 | status = workq_destroy(&workq); 111 | if (status != 0) 112 | err_abort(status, "Destroy work queue"); 113 | 114 | engine = engin_list_head; 115 | while (engine != NULL) { 116 | count++; 117 | calls += engine->calls; 118 | printf("engine %d: %d calls\n", count, engine->calls); 119 | engine = engine->link; 120 | } 121 | 122 | printf("%d engine threads processed %d calls\n", count, calls); 123 | return 0; 124 | } -------------------------------------------------------------------------------- /src/chapter03/alarm_mutex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "errors.h" 4 | 5 | typedef struct alarm_tag { 6 | struct alarm_tag *link; 7 | int seconds; 8 | time_t time; 9 | char message[64]; 10 | } alarm_t; 11 | 12 | pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER; 13 | alarm_t *alarm_list = NULL; 14 | 15 | void *alarm_thread(void *arg) 16 | { 17 | alarm_t *alarm; 18 | int sleep_time; 19 | time_t now; 20 | int status; 21 | 22 | while(1) { 23 | status = pthread_mutex_lock(&alarm_mutex); 24 | if (status != 0) 25 | err_abort(status, "Lock mutex"); 26 | 27 | alarm = alarm_list; 28 | 29 | if (alarm == NULL) 30 | sleep_time = 1; 31 | else { 32 | alarm_list = alarm_list->link; 33 | now = time(NULL); 34 | if (alarm->time <= now) 35 | sleep_time = 0; 36 | else 37 | sleep_time = alarm->time - now; 38 | #ifdef DEBUG 39 | printf("[waiting: %d(%d)\"%s\"]\n", alarm->time, sleep_time, alarm->message); 40 | #endif 41 | } 42 | 43 | status = pthread_mutex_unlock(&alarm_mutex); 44 | if (status != 0) 45 | err_abort(status, "Unlock mutex"); 46 | 47 | if (sleep_time > 0) 48 | sleep(sleep_time); 49 | else 50 | sched_yield(); 51 | 52 | if (alarm != NULL) { 53 | printf("(%d) %s\n", alarm->seconds, alarm->message); 54 | free(alarm); 55 | } 56 | } 57 | } 58 | 59 | int main() 60 | { 61 | int status; 62 | char line[128]; 63 | alarm_t *alarm, **last, *next; 64 | pthread_t thread; 65 | 66 | status = pthread_create(&thread, NULL, alarm_thread, NULL); 67 | if (status != 0) 68 | err_abort(status, "Create alarm thread"); 69 | 70 | while (1) { 71 | printf("Alarm> "); 72 | 73 | if (fgets(line, sizeof(line), stdin) == NULL) 74 | exit(0); 75 | 76 | if (strlen(line) <= 1) 77 | continue; 78 | 79 | alarm = (alarm_t*)malloc(sizeof(alarm_t)); 80 | if (alarm == NULL) 81 | errno_abort("Allocate alarm"); 82 | 83 | if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) { 84 | fprintf(stderr, "Bad command\n"); 85 | free(alarm); 86 | } else { 87 | status = pthread_mutex_lock(&alarm_mutex); 88 | if (status != 0) 89 | err_abort(status, "Lock mutex"); 90 | 91 | alarm->time = time(NULL) + alarm->seconds; 92 | 93 | last = &alarm_list; 94 | next = *last; 95 | while (next != NULL) { 96 | if (next->time >= alarm->time) { 97 | alarm->link = next; 98 | *last = alarm; 99 | break; 100 | } 101 | last = &next->link; 102 | next = next->link; 103 | } 104 | 105 | if (next == NULL) { 106 | *last = alarm; 107 | alarm->link = NULL; 108 | } 109 | 110 | #ifdef DEBUG 111 | printf("[list: ") 112 | for (next = alarm_list; next != NULL; next = next->link) 113 | printf("%d(%d)[\"%s\"]", next->time, next->time - time(NULL), next->message); 114 | printf("]\n"); 115 | #endif 116 | 117 | status = pthread_mutex_unlock(&alarm_mutex); 118 | if (status != 0) 119 | err_abort(status, "Unlock mutex"); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/chapter07/rwlock_main.c: -------------------------------------------------------------------------------- 1 | #include "rwlock.h" 2 | #include "errors.h" 3 | 4 | #define THREADS 5 5 | #define DATASIZE 15 6 | #define ITERATIONS 10000 7 | 8 | typedef struct thread_tag { 9 | int thread_num; 10 | pthread_t thread_id; 11 | int updates; 12 | int reads; 13 | int interval; 14 | } thread_t; 15 | 16 | typedef struct data_tag { 17 | rwlock_t lock; 18 | int data; 19 | int updates; 20 | } data_t; 21 | 22 | thread_t threads[THREADS]; 23 | data_t data[DATASIZE]; 24 | 25 | void *thread_routine(void *arg) 26 | { 27 | thread_t *self = (thread_t*)arg; 28 | int repeats = 0; 29 | int iteration; 30 | int element = 0; 31 | int status; 32 | 33 | for (iteration = 0; iteration < ITERATIONS; iteration++) { 34 | if (iteration % self->interval == 0) { 35 | status = rwl_writelock(&data[element].lock); 36 | if (status != 0) 37 | err_abort(status, "Write lock"); 38 | 39 | data[element].data = self->thread_num; 40 | data[element].updates++; 41 | self->updates++; 42 | 43 | status = rwl_writeunlock(&data[element].lock); 44 | if (status != 0) 45 | err_abort(status, "Write unlock"); 46 | } else { 47 | status = rwl_readlock(&data[element].lock); 48 | if (status != 0) 49 | err_abort(status, "Read lock"); 50 | 51 | self->reads++; 52 | if (data[element].data == self->thread_num) 53 | repeats++; 54 | 55 | status = rwl_readunlock(&data[element].lock); 56 | if (status != 0) 57 | err_abort(status, "Read unlock"); 58 | } 59 | 60 | element++; 61 | if (element >= DATASIZE) 62 | element = 0; 63 | } 64 | 65 | if (repeats > 0) 66 | printf("Thread %d found unchanged elements %d times\n", 67 | self->thread_num, repeats); 68 | return NULL; 69 | } 70 | 71 | int main() 72 | { 73 | int count; 74 | int data_count; 75 | int status; 76 | unsigned int seed = 1; 77 | int thread_updates = 0; 78 | int data_updates = 0; 79 | 80 | for (data_count = 0; data_count < DATASIZE; data_count++) { 81 | data[data_count].data = 0; 82 | data[data_count].updates = 0; 83 | status = rwl_init(&data[data_count].lock); 84 | if (status != 0) 85 | err_abort(status, "Init rw lock"); 86 | } 87 | 88 | for (count = 0; count < THREADS; count++) { 89 | threads[count].thread_num = count; 90 | threads[count].updates = 0; 91 | threads[count].reads = 0; 92 | threads[count].interval = rand_r(&seed) % 7; 93 | status = pthread_create(&threads[count].thread_id, 94 | NULL, thread_routine, (void *)&threads[count]); 95 | if (status != 0) 96 | err_abort(status, "Create thread"); 97 | } 98 | 99 | for (count = 0; count < THREADS; count++) { 100 | status = pthread_join(threads[count].thread_id, NULL); 101 | if (status != 0) 102 | err_abort(status, "Join thread"); 103 | 104 | thread_updates += threads[count].updates; 105 | printf("%02d: interval %d, updated %d, reads %d\n", 106 | count, threads[count].interval, 107 | threads[count].updates, threads[count].reads); 108 | } 109 | 110 | for (data_count = 0; data_count < DATASIZE; data_count++) { 111 | data_updates += data[data_count].updates; 112 | printf("data %02d: value %d, %d updates\n", 113 | data_count, data[data_count].data, 114 | data[data_count].updates); 115 | rwl_destroy(&data[data_count].lock); 116 | } 117 | 118 | printf("%d thread updates, %d data updates\n", thread_updates, data_updates); 119 | return 0; 120 | } -------------------------------------------------------------------------------- /src/chapter07/pthread_rwlock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define THREADS 5 5 | #define DATASIZE 15 6 | #define ITERATIONS 10000 7 | 8 | typedef struct thread_tag { 9 | int thread_num; 10 | pthread_t thread_id; 11 | int updates; 12 | int reads; 13 | int interval; 14 | } thread_t; 15 | 16 | typedef struct data_tag { 17 | pthread_rwlock_t lock; 18 | int data; 19 | int updates; 20 | } data_t; 21 | 22 | thread_t threads[THREADS]; 23 | data_t data[DATASIZE]; 24 | 25 | void *thread_routine(void *arg) 26 | { 27 | thread_t *self = (thread_t*)arg; 28 | int repeats = 0; 29 | int iteration; 30 | int element = 0; 31 | int status; 32 | 33 | for (iteration = 0; iteration < ITERATIONS; iteration++) { 34 | if (iteration % self->interval == 0) { 35 | status = pthread_rwlock_wrlock(&data[element].lock); 36 | if (status != 0) 37 | err_abort(status, "Write lock"); 38 | 39 | data[element].data = self->thread_num; 40 | data[element].updates++; 41 | self->updates++; 42 | 43 | status = pthread_rwlock_unlock(&data[element].lock); 44 | if (status != 0) 45 | err_abort(status, "Write unlock"); 46 | } else { 47 | status = pthread_rwlock_rdlock(&data[element].lock); 48 | if (status != 0) 49 | err_abort(status, "Read lock"); 50 | 51 | self->reads++; 52 | if (data[element].data == self->thread_num) 53 | repeats++; 54 | 55 | status = pthread_rwlock_unlock(&data[element].lock); 56 | if (status != 0) 57 | err_abort(status, "Read unlock"); 58 | } 59 | 60 | element++; 61 | if (element >= DATASIZE) 62 | element = 0; 63 | } 64 | 65 | if (repeats > 0) 66 | printf("Thread %d found unchanged elements %d times\n", 67 | self->thread_num, repeats); 68 | return NULL; 69 | } 70 | 71 | int main() 72 | { 73 | int count; 74 | int data_count; 75 | int status; 76 | unsigned int seed = 1; 77 | int thread_updates = 0; 78 | int data_updates = 0; 79 | 80 | for (data_count = 0; data_count < DATASIZE; data_count++) { 81 | data[data_count].data = 0; 82 | data[data_count].updates = 0; 83 | status = pthread_rwlock_init(&data[data_count].lock, NULL); 84 | if (status != 0) 85 | err_abort(status, "Init rw lock"); 86 | } 87 | 88 | for (count = 0; count < THREADS; count++) { 89 | threads[count].thread_num = count; 90 | threads[count].updates = 0; 91 | threads[count].reads = 0; 92 | threads[count].interval = rand_r(&seed) % 7; 93 | status = pthread_create(&threads[count].thread_id, 94 | NULL, thread_routine, (void *)&threads[count]); 95 | if (status != 0) 96 | err_abort(status, "Create thread"); 97 | } 98 | 99 | for (count = 0; count < THREADS; count++) { 100 | status = pthread_join(threads[count].thread_id, NULL); 101 | if (status != 0) 102 | err_abort(status, "Join thread"); 103 | 104 | thread_updates += threads[count].updates; 105 | printf("%02d: interval %d, updated %d, reads %d\n", 106 | count, threads[count].interval, 107 | threads[count].updates, threads[count].reads); 108 | } 109 | 110 | for (data_count = 0; data_count < DATASIZE; data_count++) { 111 | data_updates += data[data_count].updates; 112 | printf("data %02d: value %d, %d updates\n", 113 | data_count, data[data_count].data, 114 | data[data_count].updates); 115 | pthread_rwlock_destroy(&data[data_count].lock); 116 | } 117 | 118 | printf("%d thread updates, %d data updates\n", thread_updates, data_updates); 119 | return 0; 120 | } -------------------------------------------------------------------------------- /src/chapter03/alarm_cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "errors.h" 4 | 5 | typedef struct alarm_tag 6 | { 7 | struct alarm_tag *link; 8 | int seconds; 9 | time_t time; 10 | char message[64]; 11 | } alarm_t; 12 | 13 | pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER; 14 | pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER; 15 | alarm_t *alarm_list = NULL; 16 | time_t current_alarm = 0; 17 | 18 | void alarm_insert(alarm_t *alarm) 19 | { 20 | int status; 21 | alarm_t **last, *next; 22 | 23 | last = &alarm_list; 24 | next = *last; 25 | while (next != NULL) 26 | { 27 | if (next->time >= alarm->time) 28 | { 29 | alarm->link = next; 30 | *last = alarm; 31 | break; 32 | } 33 | last = &next->link; 34 | next = next->link; 35 | } 36 | 37 | if (next == NULL) 38 | { 39 | *last = alarm; 40 | alarm->link = NULL; 41 | } 42 | 43 | #ifdef DEBUG 44 | printf("[list: ") for (next = alarm_list; next != NULL; next = next->link) 45 | printf("%d(%d)[\"%s\"]", next->time, next->time - time(NULL), next->message); 46 | printf("]\n"); 47 | #endif 48 | 49 | if (current_alarm == 0 || alarm->time < current_alarm) { 50 | current_alarm = alarm->time; 51 | status = pthread_cond_signal(&alarm_cond); 52 | if (status != 0) 53 | err_abort(status, "Signal cond"); 54 | } 55 | } 56 | 57 | void *alarm_thread(void *arg) 58 | { 59 | alarm_t *alarm; 60 | struct timespec cond_time; 61 | time_t now; 62 | int status, expired; 63 | 64 | status = pthread_mutex_lock(&alarm_mutex); 65 | if (status != 0) 66 | err_abort(status, "Lock mutex"); 67 | 68 | while(1) { 69 | current_alarm = 0; 70 | while (alarm_list == NULL) { 71 | status = pthread_cond_wait(&alarm_cond, &alarm_mutex); 72 | if (status != 0) 73 | err_abort(status, "Wait on cond"); 74 | } 75 | 76 | alarm = alarm_list; 77 | alarm_list = alarm->link; 78 | now = time(NULL); 79 | expired = 0; 80 | 81 | if(alarm->time > now) { 82 | #ifdef DEBUG 83 | printf("[waiting: %d(%ld)\"%s\"]\n", alarm->time, alarm->time - now, alarm->message); 84 | #endif 85 | cond_time.tv_sec = alarm->time; 86 | cond_time.tv_nsec = 0; 87 | current_alarm = alarm->time; 88 | 89 | while (current_alarm == alarm->time) { 90 | status = pthread_cond_timedwait(&alarm_cond, &alarm_mutex, &cond_time); 91 | if (status == ETIMEDOUT) { 92 | expired = 1; 93 | break; 94 | } 95 | else if (status != 0) 96 | err_abort(status, "Cond timedwait"); 97 | } 98 | 99 | if (!expired) 100 | alarm_insert(alarm); 101 | } else { 102 | expired = 1; 103 | } 104 | 105 | if (expired) { 106 | printf("(%d) %s\n", alarm->seconds, alarm->message); 107 | free(alarm); 108 | } 109 | } 110 | } 111 | 112 | int main(int argc, char *argv[]) 113 | { 114 | int status; 115 | char line[128]; 116 | alarm_t *alarm; 117 | pthread_t thread; 118 | 119 | status = pthread_create(&thread, NULL, alarm_thread, NULL); 120 | if (status != 0) 121 | err_abort(status, "Create alarm thread"); 122 | 123 | while (1) 124 | { 125 | printf("Alarm> "); 126 | 127 | if (fgets(line, sizeof(line), stdin) == NULL) 128 | exit(0); 129 | 130 | if (strlen(line) <= 1) 131 | continue; 132 | 133 | alarm = (alarm_t *)malloc(sizeof(alarm_t)); 134 | if (alarm == NULL) 135 | errno_abort("Allocate alarm"); 136 | 137 | if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) 138 | { 139 | fprintf(stderr, "Bad command\n"); 140 | free(alarm); 141 | } 142 | else 143 | { 144 | status = pthread_mutex_lock(&alarm_mutex); 145 | if (status != 0) 146 | err_abort(status, "Lock mutex"); 147 | 148 | alarm->time = time(NULL) + alarm->seconds; 149 | 150 | alarm_insert(alarm); 151 | 152 | status = pthread_mutex_unlock(&alarm_mutex); 153 | if (status != 0) 154 | err_abort(status, "Unlock mutex"); 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /src/chapter03/backoff.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "errors.h" 3 | 4 | #define ITERATIONS 10 5 | 6 | pthread_mutex_t mutex[3] = { 7 | PTHREAD_MUTEX_INITIALIZER, 8 | PTHREAD_MUTEX_INITIALIZER, 9 | PTHREAD_MUTEX_INITIALIZER}; 10 | 11 | int backoff = 1; 12 | int yield_flag = 0; 13 | 14 | void *lock_forward(void *arg) 15 | { 16 | int i, iterate, backoffs; 17 | int status; 18 | 19 | for (iterate = 0; iterate < ITERATIONS; iterate++) 20 | { 21 | backoffs = 0; 22 | 23 | for (i = 0; i < 3; i++) 24 | { 25 | if (i == 0) 26 | { 27 | status = pthread_mutex_lock(&mutex[i]); 28 | if (status != 0) 29 | err_abort(status, "First lock"); 30 | printf("forward lock got %d\n", i); 31 | } 32 | else 33 | { 34 | if (backoff) 35 | status = pthread_mutex_trylock(&mutex[i]); 36 | else 37 | status = pthread_mutex_lock(&mutex[i]); 38 | 39 | if (status == EBUSY) 40 | { 41 | backoffs++; 42 | printf("forward locker backing of at %d\n", i); 43 | for (; i >= 0; i--) 44 | { 45 | status = pthread_mutex_unlock(&mutex[i]); 46 | if (status != 0) 47 | err_abort(status, "Backoff"); 48 | } 49 | } 50 | else 51 | { 52 | if (status != 0) 53 | err_abort(status, "Lock mutex"); 54 | printf("forward locker got %d\n", i); 55 | } 56 | } 57 | 58 | if (yield_flag) 59 | { 60 | if (yield_flag > 0) 61 | sched_yield(); 62 | else 63 | sleep(1); 64 | } 65 | } 66 | 67 | printf("lock forward got all locks, %d backoffs\n", backoffs); 68 | pthread_mutex_unlock(&mutex[0]); 69 | pthread_mutex_unlock(&mutex[1]); 70 | pthread_mutex_unlock(&mutex[2]); 71 | sched_yield(); 72 | } 73 | 74 | return NULL; 75 | } 76 | 77 | void *lock_backward(void *arg) 78 | { 79 | int i, iterate, backoffs; 80 | int status; 81 | 82 | for (iterate = 0; iterate < ITERATIONS; iterate++) 83 | { 84 | backoffs = 0; 85 | 86 | for (i = 2; i >= 0; i--) 87 | { 88 | if (i == 2) 89 | { 90 | status = pthread_mutex_lock(&mutex[i]); 91 | if (status != 0) 92 | err_abort(status, "First lock"); 93 | printf("backward lock got %d\n", i); 94 | } 95 | else 96 | { 97 | if (backoff) 98 | status = pthread_mutex_trylock(&mutex[i]); 99 | else 100 | status = pthread_mutex_lock(&mutex[i]); 101 | 102 | if (status == EBUSY) 103 | { 104 | backoffs++; 105 | printf("backward locker backing of at %d\n", i); 106 | for (; i < 3; i++) 107 | { 108 | status = pthread_mutex_unlock(&mutex[i]); 109 | if (status != 0) 110 | err_abort(status, "Backoff"); 111 | } 112 | } 113 | else 114 | { 115 | if (status != 0) 116 | err_abort(status, "Lock mutex"); 117 | printf("backward locker got %d\n", i); 118 | } 119 | } 120 | 121 | if (yield_flag) 122 | { 123 | if (yield_flag > 0) 124 | sched_yield(); 125 | else 126 | sleep(1); 127 | } 128 | } 129 | 130 | printf("lock backward got all locks, %d backoffs\n", backoffs); 131 | pthread_mutex_unlock(&mutex[2]); 132 | pthread_mutex_unlock(&mutex[1]); 133 | pthread_mutex_unlock(&mutex[0]); 134 | sched_yield(); 135 | } 136 | 137 | return NULL; 138 | } 139 | 140 | int main(int argc, char *argv[]) 141 | { 142 | pthread_t forward, backward; 143 | int status; 144 | 145 | if (argc > 1) 146 | backoff = atoi(argv[1]); 147 | 148 | if (argc > 2) 149 | yield_flag = atoi(argv[2]); 150 | 151 | status = pthread_create(&forward, NULL, lock_forward, NULL); 152 | if (status != 0) 153 | err_abort(status, "Create forward"); 154 | 155 | status = pthread_create(&backward, NULL, lock_backward, NULL); 156 | if (status != 0) 157 | err_abort(status, "Create backward"); 158 | 159 | pthread_exit(NULL); 160 | } -------------------------------------------------------------------------------- /doc/07_extended.md: -------------------------------------------------------------------------------- 1 | # 线程扩展 2 | 3 | ## 栅栏(Barriers) 4 | 5 | barrier 字面意思时栅栏,barrier 是将一组成员保持在一起的一种方式,它可以实现让一组线程等待至某个状之后再全部同时执行。一个 barrier 通常被用来确保某些井行算法中的所有合作线程在任何线程可以继续运行之前到达算法中的一个特定点。 6 | 7 | barrier 的核心是一个计数器,我们可以称之为“阈值”,即在一个 barrier 上必须等待的线程数。计时器计算着当前线程的返回数量,如果数量未达到指定点,那么之前返回的线程都必须继续等待,直到最后一个线程返回,才能开始下一步。 8 | 9 | 你可以查看源文件 `barrier.h` 、`barrier.c`、`barrier_main.c`,这是一个比较容易理解的实现。 10 | 11 | 另外 Pthreads 在 POSIX.14 草案标准(一个 “POSIX 标准子集”)中新增加了 Barriers 变量,其 API 如下: 12 | 13 | ```c 14 | #include 15 | 16 | int pthread_barrier_init(pthread_barrier_t * restrict barrier, 17 | const pthread_barrierattr_t * restrict attr, unsigned int count); 18 | 19 | int pthread_barrier_destroy(pthread_barrier_t *barrier); 20 | 21 | int pthread_barrier_wait(pthread_barrier_t *barrier); 22 | ``` 23 | 24 | 源文件 `pthread_barrier.c` 是一个 Pthreads barrier 的简单实例。 25 | 26 | ## 读/写锁(Read-Write Lock) 27 | 28 | 读/写锁很像一个互斥量,它是阻止多个线程同时修改共享数据的另外一种方。但是不同于互斥量的是它区分读数据和写数据。一个互斥量排除所有的其他线程,而一个读/写锁如果线程不需要改变数据,则允许多个线程同时读数据。当一个线程需要更新缓存数据是,则必须以独占的方式进行,其他只读线程都不能继续占有锁。 29 | 30 | 读/写锁被用来保护经常需要读但是通常不需要修改的信息(读多写少)。当写锁被释放时,如果读数据者和写数据者同时正在等待存取,则读数据者被优先给予访问权。因为潜在地允许许多线程同时完成工作,读访问优先有利于并发。 31 | 32 | 文件 `rwlock.h` 、`rwlock.c`、`rwlock_main.c` 演示了如何使用标准的 Pthreads 互斥量和状况变量实现读写锁, 这是相对容易理解的可移植的实现。 33 | 34 | 另外,在最新版本的 `X/Open XSH5 [UNIX98]` 标准中,Pthreads 增加了读写锁的支持,读写锁的数据类型为 `pthread_rwlock_t`,如果需要静态分配该类型数据,那么可通过`PTHREAD_RWLOCK_INITIALIZER` 宏来初始化它。Pthreads 读写锁 API 如下: 35 | 36 | ```c 37 | #include 38 | 39 | pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; 40 | 41 | int pthread_rwlock_init(pthread_rwlock_t * restrict lock, const pthread_rwlockattr_t * restrict attr); 42 | int pthread_rwlock_destroy(pthread_rwlock_t *lock); 43 | 44 | int pthread_rwlock_rdlock(pthread_rwlock_t *lock); 45 | int pthread_rwlock_timedrdlock(pthread_rwlock_t * restrict lock, const struct timespec * restrict abstime); 46 | int pthread_rwlock_tryrdlock(pthread_rwlock_t *lock); 47 | 48 | int pthread_rwlock_wrlock(pthread_rwlock_t *lock); 49 | int pthread_rwlock_timedwrlock(pthread_rwlock_t * restrict lock, const struct timespec * restrict abstime); 50 | int pthread_rwlock_trywrlock(pthread_rwlock_t *lock); 51 | 52 | int pthread_rwlock_unlock(pthread_rwlock_t *lock); 53 | ``` 54 | 55 | 你可以查看源文件 `pthread_rwlock.c`,这是简单的 Pthread 读写锁的使用实例。 56 | 57 | ## 自旋锁(Spin Locks) 58 | 59 | 自旋锁(Spinlock)也是一种锁,自旋锁在线程尝试获取它时,会在一个循环中不停等待(旋转),同时反复检查锁是否可用。由于线程始终保持活动状态且并没有执行有用的任务,因此使用这种锁时将产生一种忙碌的等待情况。 60 | 61 | 因为在一些多线程场景中我们需要避免操作系统进程的重新调度或者上下文的切换开销,所以如果线程仅仅只是短时间内被阻塞,那么使用自旋锁将是一种非常有效的方式。但是,如果你的程序需要比较长的时间保持锁的使用,那么自旋锁将会变的浪费,因为它会阻止其他线程运行。线程持有锁的时间越长,操作系统调度程序在保持锁定时中断线程的风险就越大。在这种情况下,其他线程将会不停“旋转”(反复尝试获取锁定),而持有锁的线程没有进行释放。结果将是无限期推迟,直到持有锁的线程完成并释放它。 62 | 63 | 下面是一个自旋锁的实现方式,该实现中使用了 GCC 提供的原子操作的相关函数: 64 | 65 | ```c 66 | typedef struct spinlock_type { 67 | int lock; 68 | } spinlock_t; 69 | 70 | static inline void spinlock_init(spinlock_t *sl) 71 | { 72 | sl->lock = 0; 73 | } 74 | 75 | static inline void spinlock_lock(spinlock_t *sl) 76 | { 77 | while(__sync_lock_test_and_set(&sl->lock, 1)) {} 78 | } 79 | 80 | static inline int spinlock_trylock(spinlock_t *sl) 81 | { 82 | return __sync_lock_test_and_set(&sl->lock, 1) == 0; 83 | } 84 | 85 | static inline void spinlock_unlock(spinlock_t *sl) 86 | { 87 | __sync_lock_release(&sl->lock); 88 | } 89 | 90 | static inline void spinlock_destroy(spinlock_t *sl) 91 | { 92 | (void) sl; 93 | } 94 | ``` 95 | 96 | 你可以查看源文件 `spinlock.h` 和 `spinlock_main.c`,这是一个自旋锁的简单使用实例,演示了10个线程并发使用自旋锁修改单一数据流程。 97 | 98 | 另外,Pthreads 也提供了自旋锁的实现,其 API 定义如下: 99 | 100 | ```c 101 | #include 102 | 103 | int pthread_spin_init(pthread_spinlock_t *lock, int pshared); 104 | int pthread_spin_lock(pthread_spinlock_t *lock); 105 | int pthread_spin_trylock(pthread_spinlock_t *lock); 106 | int pthread_spin_unlock(pthread_spinlock_t *lock); 107 | int pthread_spin_destroy(pthread_spinlock_t *lock); 108 | ``` 109 | 110 | 你可以在源文件 `pthread_spinlock.c` 中查看对自旋锁的使用实例。 111 | 112 | ## 信号量(Semaphore) 113 | 114 | 信号量是由 EW Dijkstra 在20世纪60年代后期设计的编程结构。Dijkstra 的模型是铁路运营。考虑一段铁路,其中存在单个轨道,在该轨道上一次只允许一列火车。 115 | 116 | 信号量同步此轨道上的行程。火车必须在进入单轨之前等待,直到信号量处于允许旅行的状态。当火车进入轨道时,信号量改变状态以防止其他列车进入轨道。离开这段赛道的火车必须再次改变信号量的状态,以允许另一列火车进入。 117 | 118 | 在计算机版本中,信号量似乎是一个简单的整数。线程等待许可继续,然后通过对信号量执行`P操作`来发出线程已经继续的信号。 119 | 120 | 线程必须等到信号量的值为正,然后通过从值中减去1来更改信号量的值。完成此操作后,线程执行`V操作`,通过向该值加1来更改信号量的值。这些操作必须以原子方式进行。在`P操作`中,信号量的值必须在值递减之前为正,从而产生一个值,该值保证为非负值,并且比递减之前的值小1。 121 | 122 | ### 信号量 API 123 | 124 | ```c 125 | #include 126 | 127 | int sem_init(sem_t *sem, int pshared, unsigned int value); 128 | int sem_post(sem_t *sem); 129 | int sem_wait(sem_t *sem); 130 | int sem_trywait(sem_t *sem); 131 | int sem_destroy(sem_t *sem); 132 | ``` 133 | 134 | 信号量初始化函数中,pshared的值为零,则不能在进程之间共享信号量。如果pshared的值非零,则可以在进程之间共享信号量。值 value 之名, 135 | 其中,`sem_post` 以原子方式递增sem指向的信号量。调用后,当信号量上的任何线程被阻塞时,其中一个线程被解除阻塞。 136 | 使用 `sem_wait` 来阻塞调用线程,直到sem指向的信号量计数变为大于零,然后原子地减少计数。 137 | 138 | ### 信号量解决生产者与消费者问题 139 | 140 | 生产者和消费者问题是并发编程中标准的,众所周知的一个小问题。在一个缓冲区中,分为将项目放入缓冲区生产者,从缓冲区中取出项目的消费者。 141 | 142 | 在缓冲区有可用空间之前,生产者不能在缓冲区中放置东西。在生产者写入缓冲区之前,消费者不能从缓冲区中取出东西。 143 | 144 | 你可以查看源文件 `pthread-semaphore.c`,这是一个使用信号量解决生产者、消费者问题的实例。 145 | 146 | ## 工作队列 147 | 148 | 工作队列是一组线程间分派工作的方法,创建工作队列时,可以指定需要的最大并发级别(最大线程数量)。 149 | 150 | 依据工作量的要求,线程将被开始或停止。没有发现任何请求的一个线程将等待一段时间后终止。最优的时间段取决于在你的系统上创建一个新线程的开销、维护一个不做任何工作的线程的系统资源的开销,以及你将再次需要线程的可能性。 151 | 152 | 源文件 `workq.h` 、`workq.c` 和 `workq_main.c` 显示了一个工作队列管理器的实现。 -------------------------------------------------------------------------------- /src/chapter07/rwlock.c: -------------------------------------------------------------------------------- 1 | #include "errors.h" 2 | #include "rwlock.h" 3 | 4 | int rwl_init(rwlock_t *rwl) 5 | { 6 | int status; 7 | rwl->r_active = rwl->w_active = 0; 8 | rwl->r_wait = rwl->w_wait = 0; 9 | 10 | status = pthread_mutex_init(&rwl->mutex, NULL); 11 | if (status != 0) 12 | return status; 13 | 14 | status = pthread_cond_init(&rwl->read, NULL); 15 | if (status != 0) { 16 | pthread_mutex_destroy(&rwl->mutex); 17 | return status; 18 | } 19 | 20 | status = pthread_cond_init(&rwl->write, NULL); 21 | if (status != 0) { 22 | pthread_mutex_destroy(&rwl->mutex); 23 | pthread_cond_destroy(&rwl->read); 24 | return status; 25 | } 26 | 27 | rwl->valid = RWLOCK_VALID; 28 | return 0; 29 | } 30 | 31 | int rwl_destroy(rwlock_t *rwl) 32 | { 33 | int status, status1, status2; 34 | 35 | if (rwl->valid != RWLOCK_VALID) 36 | return EINVAL; 37 | 38 | status = pthread_mutex_lock(&rwl->mutex); 39 | if (status != 0) 40 | return status; 41 | 42 | if (rwl->r_active > 0 || rwl->w_active) { 43 | pthread_mutex_unlock(&rwl->mutex); 44 | return EBUSY; 45 | } 46 | 47 | if (rwl->r_wait > 0 || rwl->w_wait > 0) { 48 | pthread_mutex_unlock(&rwl->mutex); 49 | return EBUSY; 50 | } 51 | 52 | rwl->valid = 0; 53 | 54 | status = pthread_mutex_unlock(&rwl->mutex); 55 | if (status != 0) 56 | return status; 57 | 58 | status = pthread_mutex_destroy(&rwl->mutex); 59 | status1 = pthread_cond_destroy(&rwl->read); 60 | status2 = pthread_cond_destroy(&rwl->write); 61 | 62 | return (status != 0 ? status : (status1 != 0 ? status1 : status2)); 63 | } 64 | 65 | static void rwl_readcleanup(void *arg) 66 | { 67 | rwlock_t *rwl = (rwlock_t *)arg; 68 | 69 | rwl->r_wait--; 70 | pthread_mutex_unlock(&rwl->mutex); 71 | } 72 | 73 | static void rwl_writecleanup(void *arg) 74 | { 75 | rwlock_t *rwl = (rwlock_t *)arg; 76 | 77 | rwl->w_wait--; 78 | pthread_mutex_unlock(&rwl->mutex); 79 | } 80 | 81 | int rwl_readlock(rwlock_t *rwl) 82 | { 83 | int status; 84 | 85 | if (rwl->valid != RWLOCK_VALID) 86 | return EINVAL; 87 | 88 | status = pthread_mutex_lock(&rwl->mutex); 89 | if (status != 0) 90 | return status; 91 | 92 | if (rwl->w_active) { 93 | rwl->r_wait++; 94 | pthread_cleanup_push(rwl_readcleanup, (void *)rwl); 95 | while (rwl->w_active) { 96 | status = pthread_cond_wait(&rwl->read, &rwl->mutex); 97 | if (status != 0) 98 | break; 99 | } 100 | pthread_cleanup_pop(0); 101 | } 102 | 103 | if (status == 0) 104 | rwl->r_active++; 105 | 106 | pthread_mutex_unlock(&rwl->mutex); 107 | return status; 108 | } 109 | 110 | int rwl_readtrylock(rwlock_t *rwl) 111 | { 112 | int status, status2; 113 | 114 | if (rwl->valid != RWLOCK_VALID) 115 | return EINVAL; 116 | 117 | status = pthread_mutex_lock(&rwl->mutex); 118 | if (status != 0) 119 | return status; 120 | 121 | if (rwl->w_active) 122 | status = EBUSY; 123 | else 124 | rwl->r_active++; 125 | 126 | status2 = pthread_mutex_unlock(&rwl->mutex); 127 | return (status2 != 0 ? status2 : status); 128 | } 129 | 130 | int rwl_readunlock(rwlock_t *rwl) 131 | { 132 | int status, status2; 133 | 134 | if (rwl->valid != RWLOCK_VALID) 135 | return EINVAL; 136 | 137 | status = pthread_mutex_lock(&rwl->mutex); 138 | if (status != 0) 139 | return status; 140 | 141 | rwl->r_active--; 142 | 143 | if (rwl->r_active == 0 && rwl->w_wait > 0) 144 | status = pthread_cond_signal(&rwl->write); 145 | 146 | status2 = pthread_mutex_unlock(&rwl->mutex); 147 | return (status2 == 0 ? status : status2); 148 | } 149 | 150 | int rwl_writelock(rwlock_t *rwl) 151 | { 152 | int status; 153 | 154 | if (rwl->valid != RWLOCK_VALID) 155 | return EINVAL; 156 | 157 | status = pthread_mutex_lock(&rwl->mutex); 158 | if (status != 0) 159 | return status; 160 | 161 | if (rwl->w_active || rwl->r_active > 0) { 162 | rwl->w_wait++; 163 | pthread_cleanup_push(rwl_writecleanup, (void *)rwl); 164 | while (rwl->w_active || rwl->r_active > 0) { 165 | status = pthread_cond_wait(&rwl->write, &rwl->mutex); 166 | if (status != 0) 167 | break; 168 | } 169 | pthread_cleanup_pop(0); 170 | rwl->w_wait--; 171 | } 172 | 173 | if (status == 0) 174 | rwl->w_active = 1; 175 | 176 | pthread_mutex_unlock(&rwl->mutex); 177 | 178 | return status; 179 | } 180 | 181 | int rwl_writetrylock(rwlock_t *rwl) 182 | { 183 | int status, status2; 184 | 185 | if (rwl->valid != RWLOCK_VALID) 186 | return EINVAL; 187 | 188 | status = pthread_mutex_lock(&rwl->mutex); 189 | if (status != 0) 190 | return status; 191 | 192 | if (rwl->w_active || rwl->r_active > 0) 193 | status = EBUSY; 194 | else 195 | rwl->w_active = 1; 196 | 197 | status2 = pthread_mutex_unlock(&rwl->mutex); 198 | return (status != 0 ? status : status2); 199 | } 200 | 201 | int rwl_writeunlock(rwlock_t *rwl) 202 | { 203 | int status; 204 | 205 | if (rwl->valid != RWLOCK_VALID) 206 | return EINVAL; 207 | 208 | status = pthread_mutex_lock(&rwl->mutex); 209 | if (status != 0) 210 | return status; 211 | 212 | rwl->w_active = 0; 213 | if (rwl->r_wait > 0) { 214 | status = pthread_cond_broadcast(&rwl->read); 215 | if (status != 0) { 216 | pthread_mutex_unlock(&rwl->mutex); 217 | return status; 218 | } 219 | } else if (rwl->w_wait > 0) { 220 | status = pthread_cond_signal(&rwl->write); 221 | if (status != 0) { 222 | pthread_mutex_unlock(&rwl->mutex); 223 | return status; 224 | } 225 | } 226 | 227 | status = pthread_mutex_unlock(&rwl->mutex); 228 | return status; 229 | } -------------------------------------------------------------------------------- /src/chapter07/workq.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "workq.h" 3 | #include "errors.h" 4 | 5 | int workq_init(workq_t *wq, int threads, void (*engine)(void *arg)) 6 | { 7 | int status; 8 | 9 | status = pthread_attr_init(&wq->attr); 10 | if (status != 0) 11 | return status; 12 | 13 | status = pthread_attr_setdetachstate(&wq->attr, PTHREAD_CREATE_DETACHED); 14 | if (status != 0) { 15 | pthread_attr_destroy(&wq->attr); 16 | return status; 17 | } 18 | 19 | status = pthread_mutex_init(&wq->mutex, NULL); 20 | if (status != 0) { 21 | pthread_attr_destroy(&wq->attr); 22 | return status; 23 | } 24 | 25 | status = pthread_cond_init(&wq->cv, NULL); 26 | if (status != 0) { 27 | pthread_mutex_destroy(&wq->mutex); 28 | pthread_attr_destroy(&wq->attr); 29 | return status; 30 | } 31 | 32 | wq->quit = 0; 33 | wq->first = wq->last = NULL; 34 | wq->parallelism = threads; 35 | wq->counter = 0; 36 | wq->idle = 0; 37 | wq->engine = engine; 38 | wq->valid = WORKQ_VALID; 39 | 40 | return 0; 41 | } 42 | 43 | int workq_destroy(workq_t *wq) 44 | { 45 | int status, status1, status2; 46 | 47 | if (wq->valid != WORKQ_VALID) 48 | return EINVAL; 49 | 50 | status = pthread_mutex_lock(&wq->mutex); 51 | if (status != 0) 52 | return status; 53 | 54 | wq->valid = 0; 55 | 56 | if (wq->counter > 0) { 57 | wq->quit = 1; 58 | if (wq->idle > 0) { 59 | status = pthread_cond_broadcast(&wq->cv); 60 | if (status != 0) { 61 | pthread_mutex_unlock(&wq->mutex); 62 | return status; 63 | } 64 | } 65 | 66 | while (wq->counter > 0) { 67 | status = pthread_cond_wait(&wq->cv, &wq->mutex); 68 | if (status != 0) { 69 | pthread_mutex_unlock(&wq->mutex); 70 | return status; 71 | } 72 | } 73 | } 74 | 75 | status = pthread_mutex_unlock(&wq->mutex); 76 | if (status != 0) 77 | return status; 78 | 79 | status = pthread_mutex_destroy(&wq->mutex); 80 | status1 = pthread_cond_destroy(&wq->cv); 81 | status2 = pthread_attr_destroy(&wq->attr); 82 | return (status ? status : (status1 ? status1 : status2)); 83 | } 84 | 85 | static void *workq_server(void *arg) 86 | { 87 | struct timespec timeout; 88 | workq_t *wq = (workq_t *)arg; 89 | workq_ele_t *we; 90 | int status, timedout; 91 | 92 | printf("A worker is starting\n"); 93 | status = pthread_mutex_lock(&wq->mutex); 94 | if (status != 0) 95 | return NULL; 96 | 97 | while (1) { 98 | timedout = 0; 99 | printf("Worker waiting for work\n"); 100 | clock_gettime(CLOCK_REALTIME, &timeout); 101 | timeout.tv_sec += 2; 102 | 103 | while (wq->first == NULL && !wq->quit) { 104 | status = pthread_cond_timedwait(&wq->cv, &wq->mutex, &timeout); 105 | if (status == ETIMEDOUT) { 106 | printf("Worker wait timed out\n"); 107 | timedout = 1; 108 | break; 109 | } else if (status != 0) { 110 | printf("Worker wait failed, %d (%s)\n", status, strerror(status)); 111 | wq->counter--; 112 | pthread_mutex_unlock(&wq->mutex); 113 | return NULL; 114 | } 115 | } 116 | 117 | printf("Work queue: 0x%p, quit: %d\n", wq->first, wq->quit); 118 | we = wq->first; 119 | 120 | if (we != NULL) { 121 | wq->first = we->next; 122 | if (wq->last == we) 123 | wq->last = NULL; 124 | 125 | status = pthread_mutex_unlock(&wq->mutex); 126 | if (status != 0) 127 | return NULL; 128 | 129 | printf("Worker calling engine\n"); 130 | wq->engine(we->data); 131 | free(we); 132 | 133 | status = pthread_mutex_lock(&wq->mutex); 134 | if (status != 0) 135 | return NULL; 136 | } 137 | 138 | if (wq->first == NULL && wq->quit) { 139 | printf("Worker shutting down\n"); 140 | wq->counter--; 141 | 142 | if (wq->counter == 0) 143 | pthread_cond_broadcast(&wq->cv); 144 | 145 | pthread_mutex_unlock(&wq->mutex); 146 | return NULL; 147 | } 148 | 149 | if (wq->first == NULL && timedout) { 150 | printf("engine terminating due to timeout.\n"); 151 | wq->counter--; 152 | break; 153 | } 154 | } 155 | 156 | pthread_mutex_unlock(&wq->mutex); 157 | printf("worker exiting\n"); 158 | return NULL; 159 | } 160 | 161 | int workq_add(workq_t *wq, void *element) 162 | { 163 | workq_ele_t *item; 164 | pthread_t id; 165 | int status; 166 | 167 | if (wq->valid != WORKQ_VALID) 168 | return EINVAL; 169 | 170 | item = (workq_ele_t *)malloc(sizeof(workq_ele_t)); 171 | if (item == NULL) 172 | return ENOMEM; 173 | 174 | item->data = element; 175 | item->next = NULL; 176 | 177 | status = pthread_mutex_lock(&wq->mutex); 178 | if (status != 0) { 179 | free(item); 180 | return status; 181 | } 182 | 183 | if (wq->first == NULL) 184 | wq->first = item; 185 | else 186 | wq->last->next = item; 187 | wq->last = item; 188 | 189 | if (wq->idle > 0) { 190 | status = pthread_cond_signal(&wq->cv); 191 | if (status != 0) { 192 | pthread_mutex_unlock(&wq->mutex); 193 | return status; 194 | } 195 | } else if(wq->counter < wq->parallelism) { 196 | printf("Creating new worker\n"); 197 | status = pthread_create(&id, &wq->attr, workq_server, (void*)wq); 198 | if (status != 0) { 199 | pthread_mutex_unlock(&wq->mutex); 200 | return status; 201 | } 202 | wq->counter++; 203 | } 204 | 205 | pthread_mutex_unlock(&wq->mutex); 206 | return 0; 207 | } -------------------------------------------------------------------------------- /doc/01_overview.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | ## 术语定义 4 | 5 | ### 同步与异步 6 | 7 | 同步(synchronous)意味着同时在一起工作。例如聊天室和在线会议就是同步的好例子,在聊天室中,人们对彼此的对话会立即得到反应。 8 | 同步相对来说比较简单,但开销相对较大。 9 | 10 | 异步(asynchronous) 表明事情相互独立地发生, 异步双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送。例如论坛和电子邮件就是采用异步通信的一个好例子,这样沟通的双方都会有足够的时间去思考。 11 | 异步增加了复杂性以及更加麻烦的调试过程。如果你没有同时执行多个活动, 那么异步就没有什么优势。 如果你开始了一个异步活动, 然后什么也不做等待它结束, 则你并没有从异步那儿获得太多好处。 12 | 13 | ### 并发与并行 14 | 15 | 并发(concurrency ) 的意思是指事情同时发生。 也是指让实际上可能串行发生的事情好像同时发生一样。 并发描述了单处理器系统中线程或进程的行为特点。在 POSIX 中,并发的定义要求“延迟调用线程的函数不应 16 | 该导致其他线程的无限期延迟。 17 | 18 | 并行(parallelism) 指并发序列同时执行, 换言之,软件中的“并行”语言中的“并发”是相同的意思, 而区別于软件中的“并发”。指事情在相同的方向上独立进行(没有交错)。 19 | 20 | 真正的并行只能在多处理器系统中存在, 但是并发可以在单处理器系统和多处理器系统中都存在。 21 | 并发能够在单处理器系统中存在是因为并发实际上是并行的假象。 并行要求程序能够同时执行多个操作,而并发只要求程序能够假装同时执行多个操作。 22 | 23 | ### 单处理器和多处理器 24 | 25 | 单处理器是指一台计算机只有一个编程人员可见的执行单元(处理器)。对于拥有超标量体系结构、向量或者其他数学或 I/O 协处理器的单一通用处理器,我们仍然把它当成单处理器。 26 | 27 | 多处理器是指一台计算机拥有多个处理器,它们共享同一个指令集和相同的物理内存。虽然处理器不必同等地访问所有物理内存,但是每一个应该都能访问大部分内存。 28 | 29 | ### 线程安全和可重入 30 | 31 | 线程安全是指代码能够被多个线程调用而不会产生灾难性的结果。它补要求代码在多个线程中高效的运行,只要求能够安全的运行。人部分现行函数可以利用 Pthreads 提供的互斥量、 条件变量和线程私有数据来实现线程的安全。比如,在进入函数时加锁,在退出函数时解锁。这样的函数可以被多个线程调用,但一次只能有一个线程调用它。 32 | 33 | “可重入”有时用来表示”有效的线程安全”。意味着函数不在连续的调用中保存静态数据,也不返回指向静态数据的指针。所有的数据都是由函数的调用程序提供的。重入函数不得调用非重入函数。 34 | 35 | ## 线程的好处 36 | 37 | 多线程编程模型具有以下优点: 38 | 39 | - 在多处理器系统中开发程序的并行性,除了并行性这一优点是需要特殊硬件支持外, 其他优点对硬件不做要求。 40 | - 在等待慢速外设 I/O 操作结束的同时, 程序可以执行其他计算, 为程序的并发提供更有效、 更自然的开发方式。 41 | - 一种模块化编程模型, 能清晰地表达程序中独立事件间的相互关系。 42 | 43 | ## 线程的代价 44 | 45 | 任何事情都有代价,线程也不例外。在很多情形下好处超过了代价,在其他情形下则相反。 46 | 47 | - 计算负荷。比如线程间同步会直接影响运行时间,对于两个总是同时使用的变量分别加以保护,这意味着你在同步上花费太多的时间而损失了并发。 48 | - 编程规则。尽管线程编程模型的基本思想简单,但是编写实际的代码不是件容易的事。编写能够在多个线程中良好工作的代码需要认真的思考和计划。你需要明白同步协议和程序中的不变量(invariant), 你不得不避免死锁、竞争和优先级倒置。 49 | - 更难以调试。调试不可避免地要改变事件的时序。在调试串行代码时不会有什么大问题,但是在调试异步代码时却是致命的。如果一个线程因调试陷阱而运行得稍微慢了,则你要跟踪的问题就可能不会出现。每个程序员都会遇到此类在调试时无法再现的错误,这在线程编程中会更加普遍。 50 | 51 | ## 选择线程还是不用线程 52 | 53 | 线程并非总是容易使用,而且并非总是可达到最好的性能。一些问题本身就是非并发的,添加线程线程只能降低程序的性能并使程序复杂。如果程序中的每一步都需要上一步的结果,则使用线程不会有任何帮助。每个线程不得不等待其他线程的结束。 54 | 55 | 最适合使用线程的应用包括以下这些: 56 | 57 | - 计算密集型应用,为了能在多处理器系统上运行,将这些计算分解到多个线程中实现。 58 | - I/O 密集型应用,为提高性能,将 I/O 操作重叠。很多线程可以同时等待不同的 I/O 操作。分布式服务器应用就是很好的实例,它们必须响应多个客户的请求,必须为通过慢速网络的连接主动提供 I/O 准备。 59 | 60 | ## POSIX线程概念 61 | 62 | POSIX 线程线程 API 遵循国际正式标准 POSIX 1003.1c-1995, 我们将使用非正式的术语 "Pthreads" 代表 "POSIX 1003.1c-1995"。 63 | 64 | 线程系统包含三个基本要素: 65 | 66 | - 执行环境,是并发实体的状态。 并发系统必须提供建立、 删除执行环境和独立维护它们状态的方式。 67 | - 调度,决定在某个给定时刻该执行哪个环境(或环境组), 并在不同的环境中切换。 68 | - 同步,为并发执行的环境提供了协调访问共享资源的一种机制。 69 | 70 | 下表列出了上述三方面的几个不同的实例: 71 | 72 | | 环境 | 执行环境 | 调度 | 同步 | 73 | |---|---|---|---| 74 | | 交通 |汽车 | 红绿灯 | 转变信号和刹车灯 75 | | UNIX ( 无线程 ) | 进程 | 优先级| 等待和管道 | 76 | | Pthreads | 线程 | 策略、 优先级 | 条件变量和互斥量 | 77 | 78 | ### 结构概述 79 | 80 | 使用 Pthreads, 通过调用 `pthread_create` 来创建执行环境(线程)。 创建一个线程同样也调度了该线程的执行,这将通过调用指定的 “**线程启动**” 函数开始。Pthreads 允许在创建线程时指定调度参数,或者在线程运行时设定。 81 | 当线程调用 `pthread_exit` 时退出,或者也可以从线程启动函数中返回时退出。 82 | 83 | 基本的 Pthreads 同步模型使用 **互斥量** 来保护共享数据,使用 **条件变量** 来通信,还可以使用其他的同步机制,如 **信号量**、**管道** 和 **消息队列**。 84 | 互斥量允许线程在访问共享数据时锁定它,以避免其他线程的干扰。条件变量允许线程等待共享数据到达某个期望的状态(例如队列非空或者资源可用)。 85 | 86 | ### 类型和接口 87 | 88 | POSIX 线程数据类型: 89 | 90 | | 类型 | 描述 | 91 | |---|---| 92 | | pthread_t | 线程标识符 | 93 | | pthreae_mutex_t | 互斥量 | 94 | | pthread_code_t | 条件变量 | 95 | | pthread_key_t | 线程私有权握访问键 | 96 | | pthread_attr_t | 线程属性对象 | 97 | | pthread_mutexattr_t | 互斥量属性对 | 98 | | pthread_condattr_t | 条件变属性对象 | 99 | | pthread_once_t | “一次性初始化”控制变量 | 100 | 101 | ### 错误检查 102 | 103 | 在传统的UNIX系统和原来的标准中.errno 是一个外部整型变量。由于该变量一次只能有一个值,所以只能支持进程中的单一执行流程。 104 | 传统的报错机制有许多问题,包括很难创建在报错的同时返回一个有用的 -1 值的函数。当引入多线程时会有更严重的问题。 105 | Pthreads 修订版是 POSIX 中第一个与传统的 UNIX 和 C 语言报错机制相分离的部分。 106 | Pthreads 中的新函数通过返回值来表示错误状态,而不是用变量。当成功时,Pthreads 函数返回 0, 并包含一个额外的输出参数来指向存有“有用结果” 107 | 的地址。当发生错误时,函数返回一个包含在 `errno` 变量以支持其他使用 `` 文件中的错误代码。 108 | 109 | 下面程序是一个典型的线程错误检查代码,因为 `pthread_t` 变量拥有一个无效的值,所以在使用 `pthread_join` 在遇到无效线程ID时会返回错误代码 `ESRCH`。 110 | 运行下面程序将显示错误消息:`error 3: No such process`。 111 | 112 | ```c 113 | #include 114 | #include 115 | #include 116 | #include 117 | 118 | int main() 119 | { 120 | pthread_t thread; 121 | int status; 122 | 123 | status = pthread_join(thread, NULL); 124 | if (status != 0) 125 | fprintf(stderr, "error %d: %s\n", status, strerror(status)); 126 | 127 | return status; 128 | } 129 | ``` 130 | 131 | 为了避免在实例代码的每个函数调用中都增加报错和退出的代码段,我们需要写两个报错宏使用 err_abort 检测标准的 Pthreads 错误, 使用 errno_abort 检测传统的 errno 错误变量方式。 132 | 133 | ```c 134 | #define err_abort(code, text) \ 135 | do {\ 136 | fprintf(stderr, "%s at \"%s\":%d: %s\n",\ 137 | text, __FILE__, __LINE__, strerror(code));\ 138 | abort();\ 139 | } while(0) 140 | 141 | #define errno_abort(text) \ 142 | do {\ 143 | fprintf(stderr, "%s at \"%s\":%d: %s\n",\ 144 | text, __FILE__, __LINE__, strerror(errno));\ 145 | abort();\ 146 | } while(0) 147 | ``` 148 | 149 | ## 异步编程举例 150 | 151 | 下面使用一个简单的闹钟实例程序来演示基本的异歩编程方法。该程序循环接受用户输入信息,直到出错或者输入完毕,用户输入的每行信息中,第一部分是闹钟等待的时间( 以秒为单位),第二部分是闹钟时间到迖时显示的文本消息。 152 | 153 | ### 同步版本 154 | 155 | 一直同步等待 `fgets` 产生输入,然后根据输入的秒数进行等待指定时间,最后输出闹钟响起的消息。该程序的问题是一次只能处理一个闹钟请求,如果你的程序设置了一个10分钟闹钟,就不能再继续让它在5分钟时响起另外一个闹钟。 156 | 157 | ```c 158 | #include "errors.h" 159 | 160 | int main(void) { 161 | int seconds; 162 | char line[128]; 163 | char message[64]; 164 | 165 | while(1) { 166 | printf("Alarm> "); 167 | if (fgets(line, sizeof(line), stdin) == NULL) 168 | exit(0); 169 | 170 | if (strlen(line) == 0) 171 | continue; 172 | 173 | if (sscanf(line, "%d %64[^\n]", &seconds, message) < 2) { 174 | fprintf(stderr, "Bad command\n"); 175 | } else { 176 | sleep(seconds); 177 | printf("(%d) %s\n", seconds, message); 178 | } 179 | } 180 | } 181 | ``` 182 | 183 | ### 多进程版本 184 | 185 | 为每个命令使用 `fork` 调用生成一个独立的子进程来处理闹钟。`fork` 版本是异步方式的的一种实现,该程序可以随时输入命令行,它们被彼此独立地执行。 新版本并不比同步版本复杂多少。 186 | 该版本的主要难点在于对所有己终止子进程的 `reap`。如果程序不做这个工作,则要等到程序退出的时候由系统回收,通常回收子进程的方法是调用某个 `wait` 系列函数。在本例中,我们调用 waitpid 函数,并设置 WNOHANG(父进程不必挂等待子进程的结束)。 187 | 188 | ```c 189 | #include 190 | #include 191 | #include "errors.h" 192 | 193 | int main(void) { 194 | pid_t pid; 195 | int seconds; 196 | char line[128]; 197 | char message[64]; 198 | 199 | while(1) { 200 | printf("Alarm> "); 201 | if (fgets(line, sizeof(line), stdin) == NULL) 202 | exit(0); 203 | 204 | if (strlen(line) <= 1) 205 | continue; 206 | 207 | if (sscanf(line, "%d %64[^\n]", &seconds, message) < 2) { 208 | fprintf(stderr, "Bad command\n"); 209 | } else { 210 | pid = fork(); 211 | if (pid == -1) 212 | errno_abort("Fork"); 213 | 214 | if (pid == (pid_t)0) { 215 | sleep(seconds); 216 | printf("(%d) %s\n", seconds, message); 217 | exit(0); 218 | } else { 219 | do { 220 | pid = waitpid((pid_t)-1, NULL, WNOHANG); 221 | if (pid == (pid_t) -1) 222 | errno_abort("Wait for child"); 223 | } while(pid != (pid_t)0); 224 | } 225 | } 226 | } 227 | } 228 | ``` 229 | 230 | ### 多线程版本 231 | 232 | 多线程版本与多进程十分相似,只是使用线程而非子进程来实现异步闹钟。本例中用到了以下三个Pthread函数: 233 | 234 | - `pthread_create` 函数建立一个线程, 运行由第三个参数 `alarm_thread` 指定的例程,并返回线程标识符 ID (保存在 `thread` 引用的变量中) 235 | - `pthread_self` 获取当前线程标识符 ID。 236 | - `pthread_detach` 函数允许在当线程终止时立刻回收线程资源。 237 | 238 | ```c 239 | #include "errors.h" 240 | #include 241 | 242 | typedef struct alarm_tag { 243 | int seconds; 244 | char message[64]; 245 | } alarm_t; 246 | 247 | void *alarm_thread(void *arg) { 248 | alarm_t *alarm = (alarm_t*)arg; 249 | int status; 250 | 251 | status = pthread_detach(pthread_self()); 252 | if (status != 0) 253 | err_abort(status, "Detach thread"); 254 | 255 | sleep(alarm->seconds); 256 | printf("(%d) %s\n", alarm->seconds, alarm->message); 257 | free(alarm); 258 | return NULL; 259 | } 260 | 261 | int main(void) { 262 | int status; 263 | int seconds; 264 | char line[128]; 265 | alarm_t *alarm; 266 | pthread_t thread; 267 | 268 | while(1) { 269 | printf("Alarm> "); 270 | if (fgets(line, sizeof(line), stdin) == NULL) 271 | exit(0); 272 | 273 | if (strlen(line) <= 1) 274 | continue; 275 | 276 | alarm = (alarm_t*)malloc(sizeof(alarm_t)); 277 | if (alarm == NULL) 278 | errno_abort("Allocate alarm"); 279 | 280 | if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) { 281 | fprintf(stderr, "Bad command\n"); 282 | free(alarm); 283 | } else { 284 | status = pthread_create(&thread, NULL, alarm_thread, alarm); 285 | if (status != 0) 286 | err_abort(status, "Create alarm thread"); 287 | } 288 | } 289 | } 290 | ``` -------------------------------------------------------------------------------- /doc/03_synchronization.md: -------------------------------------------------------------------------------- 1 | # 同步 2 | 3 | ## 不变量、临界区和谓词 4 | 5 | 不变量(invariant) 是由程序作出的假设, 特别是有关变量组间关系的假设。不变量可能会被破坏, 而且会经常被独立的代码段破坏。 6 | 临界区(critical section)有时称为“串行区域”,是指影响共享数据的代码段,临界区总能够对应到一个数据不变量。例如,你从队列中删除数据时, 你可以将删除数据的代码视为临界区。 7 | 谓词(Predicate) 是描述代码所需不变量的状态的语句。在英语中,谓词可以是如“队列为空”、 “资源可用” 之类的陈述。 8 | 9 | ## 互斥量 10 | 11 | 大部分多线程程序需要在线程间共享数据。如果两个线程同时访问共享数据就可能会有问,因为一个线程可能在另一个线程修改共享数据的过程中使用该数据,并认为共享数据保持末变。 12 | 使线程同步最通用和常用的方法就是确保对相同数据的内存访问“互斥地”进行,即一次只能允许一个线程写数据,其他线程必须等待。 13 | 同步不仅仅在修改数据时重要, 当线程需要读取其他线程写入的数据时,而且数据写入的顺序也有影响时,同样需要同步。 14 | 15 | ### 创建和销毁互斥量 16 | 17 | Pthreads 的互斥量用 `pthread_mutex_t` 类型的变量来表示。不能拷贝互斥量,拷贝的互斥量是不确定的,但可以拷贝指向互斥量的指针。 18 | 19 | 大部分时间互斥量在函数体外,如果有其他文件使用互斥量,声明为外部类型,如果仅在本文将内使用,则将其声明为静态类型。可以使用宏 `PTHREAD_WTEX_INZTIALIZER` 来声明具有默认属性的静态互斥量,静态初始化的互斥量不需要主动释放。 20 | 21 | 下面程序演示了一个静态创建互斥量的程序,该程序 `main` 函数为空,不会产生任何结果。 22 | 23 | ```c 24 | #include 25 | #include "errors.h" 26 | 27 | typedef struct my_struct_tag { 28 | pthread_mutex_t mutex; 29 | int value; 30 | } my_struct_t; 31 | 32 | my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, 0}; 33 | 34 | int main() 35 | { 36 | return 0; 37 | } 38 | ``` 39 | 40 | 如果要初始化一个非缺省属性的互斥量, 必须使用动态初始化。如当使用 `malloc` 动态分配一个包含互斥量的数据结构时,应该使用 `pthread_nutex_init` 调用来动态的初始化互斥量。当不需要互斥量时,应该调用 `pthread_mutex_destory` 来释放它。另外,如果想保证每个互斥量在使用前被初始化,而且只被初始化一次。可以在创建任何线程之前初始化它,如通过调用 `pthread_once`。 41 | 42 | ```c 43 | int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 44 | int pthread_mutex_destroy(pthread_mutex_t *mutex); 45 | ``` 46 | 47 | 下面程序演示了动态地初始化一个互斥量: 48 | 49 | ```c 50 | #include 51 | #include "errors.h" 52 | 53 | typedef struct my_struct_tag { 54 | pthread_mutex_t mutex; 55 | int value; 56 | } my_struct_t; 57 | 58 | int main() 59 | { 60 | my_struct_t *data; 61 | int status; 62 | 63 | data = malloc(sizeof(my_struct_t)); 64 | if (data == NULL) 65 | errno_abort("Allocate structure"); 66 | 67 | status = pthread_mutex_init(&data->mutex, NULL); 68 | if (status != 0) 69 | err_abort(status, "Init mutex"); 70 | 71 | status = pthread_mutex_destroy(&data->mutex); 72 | if (status != 0) 73 | err_abort(status, "Destroy mutex"); 74 | 75 | free(data); 76 | 77 | return status; 78 | } 79 | ``` 80 | 81 | ### 加锁和解锁互斥量 82 | 83 | 最简单的情况下使用互斥量通过调用 `pthread_mutex_lock` 或 `pthread_mutex_trylock` 锁住互斥量,处理共享数据,然后调用 `pthread_mutex_unlock` 解锁互斥量。为确保线程能够读取一组变量的一致的值,需要在任何读写这些变量的代码段周围锁住互斥量。 84 | 当调用线程己经锁住互斥量之后,就不能再加锁一个线程己经锁住互斥量之后,试图这样做的结果可能是返回错误(EDEADLK),或者可能陷入“自死锁”,使线程永远等待下去。同样,你也不能解锁一个已经解锁的互斥量,不能解锁一个由其他线程锁住的互斥量。 85 | 86 | ```c 87 | int pthread_mutex_lock(pthread_mutex_t *mutex); 88 | int pthread_mutex_trylock(pthread_mutex_t *mutex); 89 | int pthread_mutex_unlock(pthread_mutex_t *mutex); 90 | ``` 91 | 92 | 下面程序 alarm_mutex.c 是 alarm_thread.c 的一个改进版本,该程序效果如下: 93 | 94 | - 所有的闹钟按时间顺序存储在一个链表结构 `alarm_list` 中。 95 | - 互斥量 `alarm_mutex` 负责协调对闹铃请求列表 `alarm_list` 的头节点的访问。 96 | - 主线程,获取闹钟请求,将去按时间顺序插入到 `alarm_list` 中。 97 | - 子线程,检查最新的闹铃列表,如果列表为空,则并阻塞住一段时间(1秒),解锁互斥量,以便主线程添加新的闹铃请求。否则获取下一个请求的差值,阻塞指定时间后,产生闹铃。 98 | 99 | ```c 100 | #include 101 | #include 102 | #include "errors.h" 103 | 104 | typedef struct alarm_tag { 105 | struct alarm_tag *link; 106 | int seconds; 107 | time_t time; 108 | char message[64]; 109 | } alarm_t; 110 | 111 | pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER; 112 | alarm_t *alarm_list = NULL; 113 | 114 | void *alarm_thread(void *arg) 115 | { 116 | alarm_t *alarm; 117 | int sleep_time; 118 | time_t now; 119 | int status; 120 | 121 | while(1) { 122 | status = pthread_mutex_lock(&alarm_mutex); 123 | if (status != 0) 124 | err_abort(status, "Lock mutex"); 125 | 126 | alarm = alarm_list; 127 | 128 | if (alarm == NULL) 129 | sleep_time = 1; 130 | else { 131 | alarm_list = alarm_list->link; 132 | now = time(NULL); 133 | if (alarm->time <= now) 134 | sleep_time = 0; 135 | else 136 | sleep_time = alarm->time - now; 137 | 138 | status = pthread_mutex_unlock(&alarm_mutex); 139 | if (status != 0) 140 | err_abort(status, "Unlock mutex"); 141 | 142 | if (sleep_time > 0) 143 | sleep(sleep_time); 144 | else 145 | sched_yield(); 146 | 147 | if (alarm != NULL) { 148 | printf("(%d) %s\n", alarm->seconds, alarm->message); 149 | free(alarm); 150 | } 151 | } 152 | } 153 | 154 | int main() 155 | { 156 | int status; 157 | char line[128]; 158 | alarm_t *alarm, **last, *next; 159 | pthread_t thread; 160 | 161 | status = pthread_create(&thread, NULL, alarm_thread, NULL); 162 | if (status != 0) 163 | err_abort(status, "Create alarm thread"); 164 | 165 | while (1) { 166 | printf("Alarm> "); 167 | 168 | if (fgets(line, sizeof(line), stdin) == NULL) 169 | exit(0); 170 | 171 | if (strlen(line) <= 1) 172 | continue; 173 | 174 | alarm = (alarm_t*)malloc(sizeof(alarm_t)); 175 | if (alarm == NULL) 176 | errno_abort("Allocate alarm"); 177 | 178 | if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) { 179 | fprintf(stderr, "Bad command\n"); 180 | free(alarm); 181 | } else { 182 | status = pthread_mutex_lock(&alarm_mutex); 183 | if (status != 0) 184 | err_abort(status, "Lock mutex"); 185 | 186 | alarm->time = time(NULL) + alarm->seconds; 187 | 188 | last = &alarm_list; 189 | next = *last; 190 | while (next != NULL) { 191 | if (next->time >= alarm->time) { 192 | alarm->link = next; 193 | *last = alarm; 194 | break; 195 | } 196 | last = &next->link; 197 | next = next->link; 198 | } 199 | 200 | if (next == NULL) { 201 | *last = alarm; 202 | alarm->link = NULL; 203 | } 204 | 205 | status = pthread_mutex_unlock(&alarm_mutex); 206 | if (status != 0) 207 | err_abort(status, "Unlock mutex"); 208 | } 209 | } 210 | } 211 | ``` 212 | 213 | 该实例具有占用更少资源的优势,但它的响应性能不够。一旦 `alarm_thread` 线程从列表中接收了一个闹铃请求,它就进入睡眠直到闹铃到期。当它发现列表中没有闹铃请求时,也会睡眠 1 秒,以允许主线程接收新的用户请求。 当 `alarm_thread` 线程睡眠时,直到它从睡眠中返回,它都不能注意到由主线程添加到请求列表中的任何闹铃请求。这种情况下最好的办法是使用条件变量来通知共享数据的状态变化(后面章节内容)。 214 | 215 | ### 非阻塞式互斥量锁 216 | 217 | 当调用 `pthread_mutex_lock` 加锁互斥量时,如果此时互斥量己经被锁住,则调用线程将被阻塞。通常这是你希望的结果,但有时你可能希望如果互斥量己被锁住,则执行另外的代码路线,你的程序可能做其他一些有益的工作而不仅仅是等待。为此,Pthreads 提供了 `pthread_mutex_trylock` 函数,当调用互斥量己被锁住时调用该函数将返回错误代码 `EBUSY`。 218 | 219 | 下列实例程序 `trylock.c` 使用 `pthread_mutex_trylock` 函数来间歇性地报告计数器的值, 不过仅当它对计数器的访问与计数线程没有发生冲突时才报告: 220 | 221 | ```c 222 | #include 223 | #include "errors.h" 224 | 225 | #define SPIN 10000000 226 | 227 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 228 | long counter; 229 | time_t end_time; 230 | 231 | void *counter_thread(void *arg) 232 | { 233 | int status; 234 | int spin; 235 | 236 | while (time(NULL) < end_time) 237 | { 238 | status = pthread_mutex_lock(&mutex); 239 | if (status != 0) 240 | err_abort(status, "Lock mutex"); 241 | 242 | for (spin = 0; spin < SPIN; spin++) 243 | counter++; 244 | 245 | status = pthread_mutex_unlock(&mutex); 246 | if (status != 0) 247 | err_abort(status, "Unlock mutex"); 248 | 249 | sleep(1); 250 | } 251 | 252 | printf("Counter is %ld\n", counter); 253 | return NULL; 254 | } 255 | 256 | void *monitor_thread(void *arg) 257 | { 258 | int status; 259 | int misses = 0; 260 | 261 | while (time(NULL) < end_time) 262 | { 263 | sleep(3); 264 | status = pthread_mutex_trylock(&mutex); 265 | if (status != EBUSY) 266 | { 267 | if (status != 0) 268 | err_abort(status, "Trylock mutex"); 269 | 270 | printf("Counter is %ld\n", counter/SPIN); 271 | 272 | status = pthread_mutex_unlock(&mutex); 273 | if (status != 0) 274 | err_abort(status, "Unlock mutex"); 275 | } else 276 | misses++; 277 | } 278 | 279 | printf("Monitor thread missed update %d times.\n", misses); 280 | return NULL; 281 | } 282 | 283 | int main() 284 | { 285 | int status; 286 | pthread_t counter_thread_id; 287 | pthread_t monitor_thread_id; 288 | 289 | end_time = time(NULL) + 60; 290 | 291 | status = pthread_create(&counter_thread_id, NULL, counter_thread, NULL); 292 | if (status != 0) 293 | err_abort(status, "Create counter thread"); 294 | 295 | status = pthread_create(&monitor_thread_id, NULL, monitor_thread, NULL); 296 | if (status != 0) 297 | err_abort(status, "Create monitor thread"); 298 | 299 | status = pthread_join(counter_thread_id, NULL); 300 | if (status != 0) 301 | err_abort(status, "Join counter thread"); 302 | 303 | status = pthread_join(monitor_thread_id, NULL); 304 | if (status != 0) 305 | err_abort(status, "Join monitor thread"); 306 | 307 | return 0; 308 | } 309 | ``` 310 | 311 | ### 多个互斥量与死锁 312 | 313 | 有时,一个互斥量是不够的,特别是当你的代码需要跨越软件体系内部的界限时。例如,当多个线程同时访问一个队列结构时,你需要两个互斥量,一个用来保护队列头,一个用来保护队列元素内的数据。当为多线程建立一个树型结构时,你可能需要为每个节点设置一个互斥量。 314 | 315 | 使用多个互斥量会导致复杂度的增加。最坏的情况就是死锁的发生,即两个线程分别锁住了一个互斥量而等待对方的互斥量。一个线程锁住了互斥量 A 后,加锁互斥量 B;同时另一个线程锁住了 B 而等待互斥量 A,则你的代码就产生了经典的死锁现象。 316 | 317 | | 第一个线程 | 第二个线程 | 318 | |---|---| 319 | | pthread_mutex_lock(&mutex_a) | pthread_mutex_lock(&mutex_b) | 320 | | pthread_mutex_lock(&mutex_b) | pthread_mutex_lock(&mutex_a) | 321 | 322 | 针对死锁,考虑以下两种通用的解决方法: 323 | 324 | - 固定加锁顺序。所有需要同时加锁互斥量A和互斥量B的代码,必须首先加锁互斥量A,然后锁互斥量B。 325 | - 试加锁和回退。在锁住某个集合中的第一个互斥量后,使用以 `pthread_mutex_trylock` 来加锁集合中的其他互斥量,如果失败则将集合中所有己加锁互斥量释放,并重新锁。 326 | - 如果代码不变量允许先释放互斥量 1,然后再加锁互斥量 2,就可以避免同时拥有两个互斥量的需要。但是,如果存在被破坏的不变置需要锁住不变量 1,则互斥量 1 就不能被释放,直到不变量被恢复为止。在这种情况下, 你应该考虑使用回退(或者试锁-回退 )算法。 327 | 328 | 以下程序 `backoff.c` 演示了如何使用回退算法避免互斥量死锁。程序建立了两个线程线程,一个运行函数 `lock_forward`,一个个运行函数 `lock_backward`。程序每次循环都会试图锁住三个互斥量,`lock_forward` 依次锁住互斥量1、2、3,`lock_backward`则按相反顺序加锁互斥量: 329 | 330 | ```c 331 | #include 332 | #include "errors.h" 333 | 334 | #define ITERATIONS 10 335 | 336 | pthread_mutex_t mutex[3] = { 337 | PTHREAD_MUTEX_INITIALIZER, 338 | PTHREAD_MUTEX_INITIALIZER, 339 | PTHREAD_MUTEX_INITIALIZER}; 340 | 341 | int backoff = 1; 342 | int yield_flag = 0; 343 | 344 | void *lock_forward(void *arg) 345 | { 346 | int i, iterate, backoffs; 347 | int status; 348 | 349 | for (iterate = 0; iterate < ITERATIONS; iterate++) 350 | { 351 | backoffs = 0; 352 | 353 | for (i = 0; i < 3; i++) 354 | { 355 | if (i == 0) 356 | { 357 | status = pthread_mutex_lock(&mutex[i]); 358 | if (status != 0) 359 | err_abort(status, "First lock"); 360 | printf("forward lock got %d\n", i); 361 | } 362 | else 363 | { 364 | if (backoff) 365 | status = pthread_mutex_trylock(&mutex[i]); 366 | else 367 | status = pthread_mutex_lock(&mutex[i]); 368 | 369 | if (status == EBUSY) 370 | { 371 | backoffs++; 372 | printf("forward locker backing of at %d\n", i); 373 | for (; i >= 0; i--) 374 | { 375 | status = pthread_mutex_unlock(&mutex[i]); 376 | if (status != 0) 377 | err_abort(status, "Backoff"); 378 | } 379 | } 380 | else 381 | { 382 | if (status != 0) 383 | err_abort(status, "Lock mutex"); 384 | printf("forward locker got %d\n", i); 385 | } 386 | } 387 | 388 | if (yield_flag) 389 | { 390 | if (yield_flag > 0) 391 | sched_yield(); 392 | else 393 | sleep(1); 394 | } 395 | } 396 | 397 | printf("lock forward got all locks, %d backoffs\n", backoffs); 398 | pthread_mutex_unlock(&mutex[0]); 399 | pthread_mutex_unlock(&mutex[1]); 400 | pthread_mutex_unlock(&mutex[2]); 401 | sched_yield(); 402 | } 403 | 404 | return NULL; 405 | } 406 | 407 | void *lock_backward(void *arg) 408 | { 409 | int i, iterate, backoffs; 410 | int status; 411 | 412 | for (iterate = 0; iterate < ITERATIONS; iterate++) 413 | { 414 | backoffs = 0; 415 | 416 | for (i = 2; i >= 0; i--) 417 | { 418 | if (i == 2) 419 | { 420 | status = pthread_mutex_lock(&mutex[i]); 421 | if (status != 0) 422 | err_abort(status, "First lock"); 423 | printf("backward lock got %d\n", i); 424 | } 425 | else 426 | { 427 | if (backoff) 428 | status = pthread_mutex_trylock(&mutex[i]); 429 | else 430 | status = pthread_mutex_lock(&mutex[i]); 431 | 432 | if (status == EBUSY) 433 | { 434 | backoffs++; 435 | printf("backward locker backing of at %d\n", i); 436 | for (; i < 3; i++) 437 | { 438 | status = pthread_mutex_unlock(&mutex[i]); 439 | if (status != 0) 440 | err_abort(status, "Backoff"); 441 | } 442 | } 443 | else 444 | { 445 | if (status != 0) 446 | err_abort(status, "Lock mutex"); 447 | printf("backward locker got %d\n", i); 448 | } 449 | } 450 | 451 | if (yield_flag) 452 | { 453 | if (yield_flag > 0) 454 | sched_yield(); 455 | else 456 | sleep(1); 457 | } 458 | } 459 | 460 | printf("lock backward got all locks, %d backoffs\n", backoffs); 461 | pthread_mutex_unlock(&mutex[2]); 462 | pthread_mutex_unlock(&mutex[1]); 463 | pthread_mutex_unlock(&mutex[0]); 464 | sched_yield(); 465 | } 466 | 467 | return NULL; 468 | } 469 | 470 | int main(int argc, char *argv[]) 471 | { 472 | pthread_t forward, backward; 473 | int status; 474 | 475 | if (argc > 1) 476 | backoff = atoi(argv[1]); 477 | 478 | if (argc > 2) 479 | yield_flag = atoi(argv[2]); 480 | 481 | status = pthread_create(&forward, NULL, lock_forward, NULL); 482 | if (status != 0) 483 | err_abort(status, "Create forward"); 484 | 485 | status = pthread_create(&backward, NULL, lock_backward, NULL); 486 | if (status != 0) 487 | err_abort(status, "Create backward"); 488 | 489 | pthread_exit(NULL); 490 | } 491 | ``` 492 | 493 | 如果没有特殊防范机制,这个程序很快就会死锁,如果上面程序运行 `backoff 0`,就会看到死锁现象: 494 | 495 | ```shell 496 | $ ./bin/backoff 0 497 | backward lock got 2 498 | backward locker got 1 499 | forward lock got 0 500 | ``` 501 | 502 | 上面两个线程都调用 `pthread_mutex_lock` 来加锁每个互斥量,由于线程从不同的端开始,所以它们在中间遇到时就会死锁。 503 | 而使用回退算法的程序,不管运行多少次循环,上面的程序都会正常执行,而不会发生死锁现象。 504 | 505 | ## 条件变量 506 | 507 | 条件变量是用来通知共享数据状态信息的。可以使用条件变量来通知队列已空、或队列非空、或任何其他需要由线程处理的共享数据状态。 508 | 509 | 当一个线程互斥地访问其享状态时,它可能发现在其他线程改变状态之前它什么也做不了。即没有破坏不变量,但是线程就是对当前状态不感兴趣。例如,一个处理队列的线程发现队列为空时,它只能等恃,直到有一个节点被添加进队列中。 510 | 511 | 条件变置不提供互斥,需要一个互斥量来同步对共享数据的访问。 512 | 513 | 一个条件变量应该与一个谓词相关,如果试图将一个条件变量与多个谓词相关,或者将多个条件变量与一个谓词相关,就有陷入死锁或者竞争问题的危险。 514 | 515 | ### 创建和释放条件变量 516 | 517 | 程序中由 `pthread_cond_t` 类型的变量来表示条件变量。如果声明了一个使用默认属性值的静态条件变量,则需要要使用 `PTHREAD_COND_TNTTIALIZER` 宏初始化,这样初始化的条件变量不必主动释放。 518 | 519 | ```c 520 | pthread_cond_tcond = PTHREAD_COND_INITIALIZER; 521 | ``` 522 | 523 | 下面时一个静态初始化条件变量的实例: 524 | 525 | ```c 526 | #include 527 | #include "errors.h" 528 | 529 | typedef struct my_struct_tag 530 | { 531 | pthread_mutex_t mutex; 532 | pthread_cond_t cond; 533 | int value; 534 | } my_struct_t; 535 | 536 | my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; 537 | 538 | int main(int argc, char const *argv[]) 539 | { 540 | return 0; 541 | } 542 | ``` 543 | 544 | 有时无法静态地初始化一个条件变量,例如,当使用 `malloc` 分配一个包含条件变量的结构时,这时,你需要调用 `pthread_cond_init` 来动态地初始化条件变量。当动态初始化条件变量时,应该在不需要它时调用 `pthread_cond_destory` 来释放它。 545 | 546 | ```c 547 | int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *condattr); 548 | int pthread_cond_destroy(pthread_cond_t *cond); 549 | ``` 550 | 551 | 下面是一个动态初始化条件变量的实例: 552 | 553 | ```c 554 | #include 555 | #include "errors.h" 556 | 557 | typedef struct my_struct_tag 558 | { 559 | pthread_mutex_t mutex; 560 | pthread_cond_t cond; 561 | int value; 562 | } my_struct_t; 563 | 564 | int main(int argc, char const *argv[]) 565 | { 566 | my_struct_t *data; 567 | int status; 568 | 569 | data = malloc(sizeof(my_struct_t)); 570 | if (data == NULL) 571 | errno_abort("Allocate structure"); 572 | 573 | status = pthread_mutex_init(&data->mutex, NULL); 574 | if (status != 0) 575 | err_abort(status, "Init mutex"); 576 | 577 | status = pthread_cond_init(&data->cond, NULL); 578 | if (status != 0) 579 | err_abort(status, "Init condition"); 580 | 581 | status = pthread_cond_destroy(&data->cond); 582 | if (status != 0) 583 | err_abort(status, "Destroy condition"); 584 | 585 | status = pthread_mutex_destroy(&data->mutex); 586 | if (status != 0) 587 | err_abort(status, "Destroy mutex"); 588 | 589 | free(data); 590 | 591 | return status; 592 | } 593 | ``` 594 | 595 | ### 等待条件变量和唤醒等待线程 596 | 597 | 每个条件变量必须与一个特定的互斥量、一个谓词条件相关联。当线程等待条件变量时,它必须轉相关互斥量锁住。记住,在阻寒线程之前,条件变量等待操作将解锁互斥量;而在重新返回线程之前,会再次锁住互斥量。 598 | 599 | 所有并发地(同时)等待同一个条件变量的线程心须指定同一个相关互斥量。例如,Pthreads不允许线程1使用互斥量 A 等待条件变量 A,而线程2使用互斥量 B 等待条件变量 A。不过,以下情况是十分合理的:线程1使用互斥量 A 等待条件变量 A,而线程2使用互斥量 A 等待条件变量 B。即,任何条件变量在特定时刻只能与一个互斥量相关联,而互斥量则可以同时与多个条件变过关联。 600 | 601 | ```c 602 | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 603 | int pthread_cond_timedwait(pthread_cond *cond, pthread_mutex_t *mutex, struct timespec *expiration); 604 | ``` 605 | 606 | 一旦有线程为某个谓词在等待一个条件变量,你可能需要唤醒它。Pthreads 提供了两种方式唤醒等待的线程:一个是“发信号”,一个是“广播”。发信号只唤醒一个等待该条件变量的线程,而广播将唤醒所有等待该条件变量的线程。 607 | 608 | 广播与发信号真正的区别是效率:广播将唤醒额外的等待线程,而这些线程会检测自己的谓词然后继续等待,通常,不能用发信号代替广播。“当有什么疑惑的时候,就使用广播”。 609 | 610 | ```c 611 | int pthread_cond_signal(pthread_cond_t *cond); 612 | int pthread_cond_broadcast(pthread_cond_t *cond); 613 | ``` 614 | 615 | 下面实例展示了如何等待条件变量,唤醒正在睡眠的等待线程。 616 | 线程 `wait_thread` 等待指定时间后,设置 `value` 值后,发送信号给条件变量。 617 | 主线程调用 `pthread_cond_timedwait` 函数等待最多2秒,如果 `hibernation` 大于2秒则条件变量等待将会超时,返回 `ETIMEOUT`; 618 | 如果 `hibernation` 设置为2秒,则主线程与 `wait_thread` 线程发生竞争,每次运行结果可能不同; 619 | 如果 `hibertnation` 设置少于2秒,则条件变量等待永远不会超时。 620 | 621 | ```c 622 | #include 623 | #include 624 | #include "errors.h" 625 | 626 | typedef struct my_struct_tag 627 | { 628 | pthread_mutex_t mutex; 629 | pthread_cond_t cond; 630 | int value; 631 | } my_struct_t; 632 | 633 | my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; 634 | 635 | int hibernation = 1; 636 | 637 | void *wait_thread(void *arg) 638 | { 639 | int status; 640 | 641 | sleep(hibernation); 642 | 643 | status = pthread_mutex_lock(&data.mutex); 644 | if (status != 0) 645 | err_abort(status, "Lock mutex"); 646 | 647 | data.value = 1; 648 | 649 | status = pthread_cond_signal(&data.cond); 650 | if (status != 0) 651 | err_abort(status, "Signal condition"); 652 | 653 | status = pthread_mutex_unlock(&data.mutex); 654 | if (status != 0) 655 | err_abort(status, "Unlock mutex"); 656 | 657 | return NULL; 658 | } 659 | 660 | int main(int argc, char *argv[]) 661 | { 662 | int status; 663 | pthread_t wait_thread_id; 664 | struct timespec timeout; 665 | 666 | if (argc > 1) 667 | hibernation = atoi(argv[1]); 668 | 669 | status = pthread_create(&wait_thread_id, NULL, wait_thread, NULL); 670 | if (status != 0) 671 | err_abort(status, "Create wait thread"); 672 | 673 | timeout.tv_sec = time(NULL) + 2; 674 | timeout.tv_nsec = 0; 675 | 676 | status = pthread_mutex_lock(&data.mutex); 677 | if (status != 0) 678 | err_abort(status, "Lock mutex"); 679 | 680 | while (data.value == 0) { 681 | status = pthread_cond_timedwait(&data.cond, &data.mutex, &timeout); 682 | if (status == ETIMEDOUT) { 683 | printf("Condition wait time out.\n"); 684 | break; 685 | } else if (status != 0) 686 | err_abort(status, "Wait on condition"); 687 | } 688 | 689 | status = pthread_mutex_unlock(&data.mutex); 690 | if (status != 0) 691 | err_abort(status, "Unlock mutex"); 692 | 693 | return 0; 694 | } 695 | ``` 696 | 697 | ### 闹钟实例最终版本 698 | 699 | 之前采用 `mutex` 实现的闹钟版本并不完美,它必须在处理完当前闹铃后,才能检测其他闹铃请求是否已经被加入了列表,即使新的请求 700 | 的到期时间比当前请求早。例如, 首先输入命令行 `10 message1`, 然后输入 `5 message2`,那么程序是无法预知后面5秒的闹钟加入到列表中来了,只能先处理完10秒的闹钟,才能继续处理后面的内容。 701 | 702 | 我们可以增加条件变量的使用来解决这个问题,新的版本使用一个超时条件变量操作代替睡眠操作,以等待闹钟到时。 703 | 当主线程在列表中添加了一个新的请求时,将发信号给条件变量,立刻唤醒 `alarm_thread` 线程。`alarm_thread` 线程可以重排等待的闹铃请求,然后重新等待。 704 | 705 | 你可以在 `alarm_cond.c` 获取源代码实现。 -------------------------------------------------------------------------------- /doc/05_advanced_thread_programming.md: -------------------------------------------------------------------------------- 1 | # 线程高级编程 2 | 3 | ## 一次性初始化 4 | 5 | 一些事情仅仅需要做一次,不管是什么。在主函数中并且在调用任何其他依赖于初始化的事物之前,这时初始化应用最容易,特别是在创造任何线程之前初始化它需要的数据,如互斥量、线程特定数据键等。 6 | 7 | 在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态地初始化为 0,而任何依赖于初始化的代码都能测试该变量。如果变量值仍然为 0, 则它能实行初始化,然后将变量置为 1,以后检查的代码将跳过初始化。如下面代码示例: 8 | 9 | ```c 10 | bool initialized = false; 11 | 12 | void init() 13 | { 14 | if (initialized) 15 | return; 16 | 17 | // TODO 18 | initialized = true 19 | } 20 | ``` 21 | 22 | 但在使用多线程时,上述操作就不是那么容易了。如果多个线程并发地执行初始化序列代码, 2 个线程可能都发现 `initialized` 为0,并且都执行初始化,而且该过程本该仅仅执行一次,那么上面代码就会立马发生不可预估的错误。 23 | 24 | 对于多线程环境下初始化的状态有两种方式: 25 | 26 | - 使用一个静态初始化的互斥量来编写一次性初始化代码。 27 | - 无法静态初始化一个互斥量时,使用 `pthread_once`。 28 | 29 | 对于 `pthread_once` 初始化,需要声明类型为 `pthread_once_t` 的一个控制变量,且该控制变量必须使用 `PTHREAD_ONCE_INIT` 宏进行静态初始化。`pthread_once` 首先检查控制变量,以判断是否已经完成初始化,如果完成,则什么都不做并立刻返回;否则,`pthread_once` 会调用初始化函数,并且记录初始化完成。如果在一个线程初始化时,另外一个线程也调用了`pthread_once`,则调用线程会阻塞等待,直到正在初始化的线程返回,这样就确保了所以状态一定会正确初始化完成。 30 | 31 | 下面是一个使用 `pthread_once` 来初始化的实例: 32 | 33 | ```c 34 | #include 35 | #include "errors.h" 36 | 37 | pthread_once_t once_block = PTHREAD_ONCE_INIT; 38 | pthread_mutex_t mutex; 39 | 40 | void once_init_routine(void) 41 | { 42 | int status; 43 | 44 | status = pthread_mutex_init(&mutex, NULL); 45 | if (status != 0) 46 | err_abort(status, "Init mutex"); 47 | } 48 | 49 | void *thread_routine(void *arg) 50 | { 51 | int status; 52 | 53 | status = pthread_once(&once_block, once_init_routine); 54 | if (status != 0) 55 | err_abort(status, "Once init"); 56 | 57 | status = pthread_mutex_lock(&mutex); 58 | if (status != 0) 59 | err_abort(status, "Init mutex"); 60 | 61 | printf("thread_toutine has locked the mutex.\n"); 62 | 63 | status = pthread_mutex_unlock(&mutex); 64 | if (status != 0) 65 | err_abort(status, "Destroy mutex"); 66 | 67 | return NULL; 68 | } 69 | 70 | int main() 71 | { 72 | int status; 73 | pthread_t thread_id; 74 | 75 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 76 | if (status != 0) 77 | err_abort(status, "Create thread"); 78 | 79 | status = pthread_once(&once_block, once_init_routine); 80 | if (status != 0) 81 | err_abort(status, "Once init"); 82 | 83 | status = pthread_mutex_lock(&mutex); 84 | if (status != 0) 85 | err_abort(status, "Init mutex"); 86 | 87 | printf("Main has locked the mutex.\n"); 88 | 89 | status = pthread_mutex_unlock(&mutex); 90 | if (status != 0) 91 | err_abort(status, "Destroy mutex"); 92 | 93 | status = pthread_join(thread_id, NULL); 94 | if (status != 0) 95 | err_abort(status, "Join thread"); 96 | 97 | return 0; 98 | } 99 | ``` 100 | 101 | 上面程序,唯一的临界共享数据实际是 `once_block`,主线程和线程 `thread_routine` 都会调用 `pthread_once` 进行初始化,但只会有一个线程会执行初始化函数。 102 | 103 | ## 属性 104 | 105 | 当我们创建线程或动态初始化互斥量和条件变量时,通常使用空指针作为第二个参数,这个参数实际上是指向一个属性对象的指针。空指针表明,Pthreads 应该为所有属性假定默认值,就像静态初始化互斥量或条件变量时一样。 106 | 107 | 一个属性对象是当初始化一个对象时提供的一个扩展参数表,可以提供更加高级的功能。类型 `pthread_attr_t` 代表一个属性对象,线程、 互斥置和条件变量都有自己特殊的属性对象类型, 分别是 `pthread_attr_t`、 `pthread_mutexattr_t` 和 `pthread_condattr_t`。 108 | 109 | ### 互斥量属性 110 | 111 | Pthreads 为互斥量创建定义下列属性:`pshared`、`pratocol` 和 `prioceiling`。通过调用 `pthread_mutexattr_init` 初始化互斥量属性,指定一个指向类型 `pthread_mutexattr_t` 变量的指针。 112 | 113 | ```c 114 | pthread_mutexattr_t mutex_attr; 115 | int pthread_mutexattr_init(pthread_mutexattr_t *attr); 116 | int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); 117 | int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared) 118 | int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); 119 | ``` 120 | 121 | 下面程序显示了如何设置属性对象来创建使用 `pshared` 属性的互斥量,并且获取 `pshared` 值,打印输出 1: 122 | 123 | ```c 124 | #include 125 | #include "errors.h" 126 | 127 | pthread_mutex_t mutex; 128 | 129 | int main() 130 | { 131 | int status; 132 | int pshared; 133 | pthread_mutexattr_t mutex_attr; 134 | 135 | status = pthread_mutexattr_init(&mutex_attr); 136 | if (status != 0) 137 | err_abort(status, "Init mutex attr"); 138 | 139 | status = pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); 140 | if (status != 0) 141 | err_abort(status, "Set pshared"); 142 | 143 | status = pthread_mutex_init(&mutex, &mutex_attr); 144 | if (status != 0) 145 | err_abort(status, "Init mutex"); 146 | 147 | status = pthread_mutexattr_getpshared(&mutex_attr, &pshared); 148 | if (status != 0) 149 | err_abort(status, "Get pshared"); 150 | 151 | printf("pshared: %d\n", pshared); 152 | 153 | return 0; 154 | } 155 | ``` 156 | 157 | ### 条件变量属性 158 | 159 | Pthreads 为条件变量的创建仅定义了一个属性 `pshared`。使用 `pthread_condattr_init` 初始化条件变量属性对象,设置一个指向类型 `pthread_condattr_t` 变量的指针。可以通过调用 `pthread_condattr_setpshared` 设置 `pshared`。 160 | 161 | 该属性默认值时 `PTHREAD_PROCESS_PRIVATE`,如果条件变量属性需要被多个线程使用,可以设置值为 `PTHREAD_PROCESS_SHAREAD`。 162 | 163 | 下面程序演示了如何使用条件变量的 `pshared` 属性来创建设置一个条件变量: 164 | 165 | ```c 166 | #include 167 | #include "errors.h" 168 | 169 | pthread_cond_t cond; 170 | 171 | int main() 172 | { 173 | int status; 174 | pthread_condattr_t cond_attr; 175 | 176 | status = pthread_condattr_init(&cond_attr); 177 | if (status != 0) 178 | err_abort(status, "Create attr"); 179 | 180 | status = pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_PRIVATE); 181 | if (status != 0) 182 | err_abort(status, "Set pshared"); 183 | 184 | status = pthread_cond_init(&cond, &cond_attr); 185 | if (status != 0) 186 | err_abort(status, "Init cond"); 187 | 188 | return 0; 189 | } 190 | ``` 191 | 192 | ### 线程属性 193 | 194 | POSIX 为线程创建定义下列属性:detachstate、stacksize、stackaddr、scope、inheritsched、schedpolicy 和 schedparam。并不是所有系统都支持以上所有的属性,因此需要在使用前检查系统文档。 195 | 196 | 所有的 Pthreads 系统都支持 `detachstate` 属性 ,该属性的值可以是 `PTHREAD_CREATE_JOINABLE` 或 `PTHREAD_CREATE_DETACHED`。 197 | 默认的线程被创建为可连接的(joinable),即意味着由 `pthread_create` 创建的该线程ID 能被用来与线程连接并获得它的返回值,或取消它。 198 | 如果设置为 `PTHREAD_CREATE_DETACHED`,则该属性对象创建的线程 ID 不能被使用,线程终止时,线程的所有资源都会被系统立刻回收。所以,在创建已经知道不需要取消或连接的线程时,应该以可分离的方式创建它们。 199 | 200 | ```c 201 | pthread_attr_t attr; 202 | int pthread_attr_init(pthread_attr_t *attr); 203 | int pthread_attr_destroy(pthread_attr_t *attr); 204 | int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); 205 | int pthread_attr_setdetachstate(pthread_attr_t *attr, int *detachstate); 206 | ``` 207 | 208 | 如果系统定义了标志 `_POSIX_THREAD_ATTR_STACKSIZE`,就可以设置 `stacksize` 属性,指定使用属性对象创建的线程栈的最小值。但栈大小不是可移植的,你应该小心使用它。 209 | 如果系统定义了标志 `_POSIX_THREAD_ATTR_STACKADDR`,就可以设置 `stackaddr` 属性,为指定线程指定一个存储器区域来作为堆栈使用。 210 | 211 | 下面程序演示了实际中的某些属性对象的使用实例: 212 | 213 | ```c 214 | #include 215 | #include 216 | #include "errors.h" 217 | 218 | void *thread_routine(void *arg) 219 | { 220 | printf("The thread is here\n"); 221 | return NULL; 222 | } 223 | 224 | int main() 225 | { 226 | pthread_t thread_id; 227 | pthread_attr_t thread_attr; 228 | struct sched_param thread_param; 229 | size_t stack_size; 230 | int status; 231 | 232 | status = pthread_attr_init(&thread_attr); 233 | if (status != 0) 234 | err_abort(status, "Create attr"); 235 | 236 | status = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); 237 | if (status != 0) 238 | err_abort(status, "Set detach"); 239 | 240 | #ifdef _POSIX_THREAD_ATTR_STACKSIZE 241 | status = pthread_attr_getstacksize(&thread_attr, &stack_size); 242 | if (status != 0) 243 | err_abort(status, "Get stack size"); 244 | 245 | printf("Default stack size is %d; minimum is %d\n", stack_size, PTHREAD_STACK_MIN); 246 | 247 | status = pthread_attr_setstacksize(&thread_attr, PTHREAD_STACK_MIN*2); 248 | if (status != 0) 249 | err_abort(status, "Set stack size"); 250 | #endif 251 | 252 | status = pthread_create(&thread_id, &thread_attr, thread_routine, NULL); 253 | if (status != 0) 254 | err_abort(status, "Create thread"); 255 | 256 | printf("Main exiting\n"); 257 | pthread_exit(NULL); 258 | return 0; 259 | } 260 | ``` 261 | 262 | ## 取消 263 | 264 | 大部分时间每个线程独立地运行着,完成一个特定的工作,并且自己退出。但是有时一个线程被创建并不需要一定完成某件事情。例如用户可以单击按钮取消停止长时间的搜索操作。取消一个线程就像告诉一个人停止他正在做的工作一样。Pthreads 允许每个线程控制自己的结束,它能恢复程序不变量并解锁互斥量。当线程完成一些重要的操作时它甚至能推迟取消。 265 | 266 | 以下是常用的线程取消函数: 267 | 268 | ```c 269 | int pthread_cancel(pthread_t thread); 270 | int pthread_setcancelstate(int state, int *oldstate); 271 | int pthread_setcanceltype(int type, int *oldtype); 272 | void pthread_testcancel(void); 273 | void pthread_cleanup_push(void (*routine)(void *), void *arg); 274 | void pthread_cleanup_pop(int execute); 275 | ``` 276 | 277 | Pthread 支持三种取消模式,模式为两位二进制编码,称为“取消状态”和“取消类型”。每种模式实质上包括开、关两种状态。取消状态可以是“启用”( enable)或“禁用”(disable),取消类型可以是被“推迟” 或 “异步。如果取消状态被禁用,那么其他取消模式都会失效,相反则可以执行“推迟”或“异步”模式。 278 | 279 | | 模式 | 状态 | 类型 | 含义 | 280 | |---|---|---|---| 281 | | Off(关)| 禁用 | 二者之一 | 取消被推迟,直到启动取消模式 | 282 | | Deferred(推迟) | 启用 | 推迟 | 在下一个取消点执行取消 | 283 | | Asynchronous(异步) | 启用 | 异步 | 可以随时执行取消 | 284 | 285 | 为取消一个线程, 你需要线程的标识符 ID, 即由 `pthread_create` 返回给创建者或由 `pthread_self` 返回给线程自己的 `pthread_t` 值。如果没有一个线程的标识符 TD, 就不能取消线程。 286 | 287 | 取消一个线程是异步的, 当 `pthread_cancel` 调用返回时, 线程未必已经被取消,可能仅仅被通知有一个针对它的未解决的取消请求。如果需要知道线程在何时实际终止,就必须在取消它之后调用 `pthread_join` 与它连接。 288 | 289 | 也有被称为 `pthread_testcancel` 的特殊函数, 该函数仅仅是一个推迟的取消点。如果线程没被要求终止,它将很快返回,这允许你将任何函数转变为取消点。 290 | 291 | 下面是一个在循环内调用 `pthread_testcancel` 来对一个延迟取消反应的线程实例: 292 | 293 | ```c 294 | #include 295 | #include "errors.h" 296 | 297 | static int counter; 298 | 299 | void *thread_routine(void *arg) 300 | { 301 | printf("thread_routine starting\n"); 302 | for (counter == 0; ; counter++) 303 | { 304 | if ((counter % 1000) == 0) 305 | { 306 | printf("calling testcancel\n"); 307 | pthread_testcancel(); 308 | } 309 | } 310 | } 311 | 312 | int main() 313 | { 314 | pthread_t thread_id; 315 | void *result; 316 | int status; 317 | 318 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 319 | if (status != 0) 320 | err_abort(status, "Create thread"); 321 | 322 | sleep(2); 323 | 324 | printf("callling cancel\n"); 325 | 326 | status = pthread_cancel(thread_id); 327 | if (status != 0) 328 | err_abort(status, "Cancel thread"); 329 | 330 | printf("calling join\n"); 331 | 332 | status = pthread_join(thread_id, &result); 333 | if (status != 0) 334 | err_abort(status, "Join thread"); 335 | 336 | if (result == PTHREAD_CANCELED) 337 | printf("Thread canceled at iteration %d\n", counter); 338 | else 339 | printf("Thread was not canceled\n"); 340 | 341 | return 0; 342 | } 343 | ``` 344 | 345 | ### 推迟取消 346 | 347 | “推迟取消”意味着线程的取消类型被设置为 `PTHREAD_DEFERRED`,线程的取消使能属性被设置为 `PTHREAD_CANCEL_ENABLE`,线程将仅仅在到达取消点时才响应取消请求。 348 | 349 | 大多数取消点包含可以“无限”时间阻塞线程的 I/O 操作,它们是可取消的,以便等待能被打断,比如 `wait`、`read` 这样的函数函数。 350 | 你可以在下面这个链接中查找到所有可能的取消点: 351 | [Pthreads-Cancellation-points](http://man7.org/linux/man-pages/man7/pthreads.7.html) 352 | 353 | 如果需要保证取消不能在一个特别的取消点或取消点的一些顺序期间发生,可以暂时在代码的那个区域停用取消。下面程序是一个实例: 354 | 355 | ```c 356 | #include 357 | #include "errors.h" 358 | 359 | static int counter; 360 | 361 | void *thread_routine(void *arg) 362 | { 363 | int state; 364 | int status; 365 | 366 | printf("thread_routine starting\n"); 367 | for (counter == 0;; counter++) 368 | { 369 | if ((counter % 755) == 0) 370 | { 371 | status = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); 372 | if (status != 0) 373 | err_abort(status, "Disable cancel"); 374 | 375 | sleep(1); 376 | 377 | status = pthread_setcancelstate(state, &state); 378 | if (status != 0) 379 | err_abort(status, "Restore cancel"); 380 | } 381 | else if ((counter % 1000) == 0) 382 | { 383 | printf("calling testcancel\n"); 384 | pthread_testcancel(); 385 | } 386 | } 387 | } 388 | 389 | int main() 390 | { 391 | pthread_t thread_id; 392 | void *result; 393 | int status; 394 | 395 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 396 | if (status != 0) 397 | err_abort(status, "Create thread"); 398 | 399 | sleep(2); 400 | 401 | printf("callling cancel\n"); 402 | 403 | status = pthread_cancel(thread_id); 404 | if (status != 0) 405 | err_abort(status, "Cancel thread"); 406 | 407 | printf("calling join\n"); 408 | 409 | status = pthread_join(thread_id, &result); 410 | if (status != 0) 411 | err_abort(status, "Join thread"); 412 | 413 | if (result == PTHREAD_CANCELED) 414 | printf("Thread canceled at iteration %d\n", counter); 415 | else 416 | printf("Thread was not canceled\n"); 417 | 418 | return 0; 419 | } 420 | ``` 421 | 422 | ### 异步取消 423 | 424 | 如果目标线程不需要使用取消点来査询取消请求。对于运行一个紧密计算循环的线程(例如,在找一个素数因素)而言是非常珍贵的,因为那种情况下调用 `pthread_testcancel` 的开销在可能是严重的。 425 | 426 | 异步取消线程很难确保目标线程安全的执行取消,例如当你调用 `malloc` 时,系统为你分配一些堆内存,但 `malloc` 可能在很多地方被异步取消打断,可能在分配内存前,或可能在分配内存后、也可能在保存地址返回前被打断。无论哪种情况,你的代码保存的内存地址变量将是未初始化的,这就很可能造成内存泄漏。 427 | 428 | 所以,在你的任何代码里面应该 **避免异步的取消** !我们很难正确使用异步取消,并且很少有用。 429 | 430 | 除非当函数被记录为“异步取消安全”的,否则当异步取消被启用时你不该调用任何函数。Pthreads 建议所有的库函数应该记录它们是否是异步取消安全的。如果函数的描述没有具体的说明,则你应该总是假定它不是异步取消安全的。 431 | 432 | 下面是一个计算密集的循环中异步取消的使用实例,但是如果在循环内有任何函数调用,程序将变得不可靠,而推迟取消的版本将能继续正确工作: 433 | 434 | ```c 435 | #include 436 | #include "errors.h" 437 | 438 | #define SIZE 10 439 | 440 | static int matrixa[SIZE][SIZE]; 441 | static int matrixb[SIZE][SIZE]; 442 | static int matrixc[SIZE][SIZE]; 443 | 444 | void *thread_routine(void *arg) 445 | { 446 | int cancel_type, status; 447 | int i, j, k, value = 1; 448 | 449 | for (i = 0; i < SIZE; i++) 450 | for (j = 0; j < SIZE; j++) 451 | { 452 | matrixa[i][j] = i; 453 | matrixb[i][j] = j; 454 | } 455 | 456 | while (1) { 457 | status = pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, &cancel_type); 458 | if (status != 0) 459 | err_abort(status, "Set cancel type"); 460 | 461 | for (i = 0; i < SIZE; i++) 462 | { 463 | for (j = 0; j < SIZE; j++) 464 | { 465 | matrixc[i][j]= 0; 466 | for (k = 0; k < SIZE; k++) 467 | matrixc[i][j] += matrixa[i][k] * matrixb[k][j]; 468 | } 469 | } 470 | 471 | status = pthread_setcancelstate(cancel_type, &cancel_type); 472 | if (status != 0) 473 | err_abort(status, "Set cancel type"); 474 | 475 | for (i = 0; i < SIZE; i++) 476 | for (j = 0; j < SIZE; j++) 477 | matrixa[i][j] = matrixc[i][j]; 478 | } 479 | } 480 | 481 | int main() 482 | { 483 | pthread_t thread_id; 484 | void *result; 485 | int status; 486 | 487 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 488 | if (status != 0) 489 | err_abort(status, "Create thread"); 490 | 491 | sleep(1); 492 | 493 | printf("callling cancel\n"); 494 | 495 | status = pthread_cancel(thread_id); 496 | if (status != 0) 497 | err_abort(status, "Cancel thread"); 498 | 499 | printf("calling join\n"); 500 | 501 | status = pthread_join(thread_id, &result); 502 | if (status != 0) 503 | err_abort(status, "Join thread"); 504 | 505 | if (result == PTHREAD_CANCELED) 506 | printf("Thread canceled\n"); 507 | else 508 | printf("Thread was not canceled\n"); 509 | 510 | return 0; 511 | } 512 | ``` 513 | 514 | ### 清除 515 | 516 | 当一个代码段被取消时,需要恢复一些状态,必须使用清除处理器。例如当线程在等待一个条件变量时被取消,它将被唤醒,并保持互斥量加锁状态。在线程终止前,通常需要恢复不变量,且它总是需要释放互斥量。 517 | 518 | 可以把每个线程考虑为有一个活动的清除处理函数的栈。 调用 `pthread_cleanup_push` 将清除处理函数加到栈中, 调用 `pthread_cleanup_pop` 删除最近增加的处理函数。当线程被取消时或当它调用 `pthread_exit` 退出时,Pthreads 从最近增加的清除处理函数幵始,依次调用各个活动的清除处理函数,当所有活动的清除处理函数返回时,线程被终止。 519 | 520 | 下面程序演示了当一个条件变量等待被取消时,使用清除处理函数来释放互斥量: 521 | 522 | ```c 523 | #include 524 | #include "errors.h" 525 | 526 | #define THREADS 5 527 | 528 | typedef struct control_tag 529 | { 530 | int counter, bysy; 531 | pthread_mutex_t mutex; 532 | pthread_cond_t cv; 533 | } control_t; 534 | 535 | control_t control = {0, 1, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER}; 536 | 537 | void cleanup_handler(void *arg) 538 | { 539 | control_t *st = (control_t *)arg; 540 | int status; 541 | 542 | st->counter--; 543 | printf("cleaup_handler: counter == %d\n", st->counter); 544 | 545 | status = pthread_mutex_unlock(&st->mutex); 546 | if (status != 0) 547 | err_abort(status, "Unlock in cleanup handler"); 548 | } 549 | 550 | void *thread_routine(void *arg) 551 | { 552 | int status; 553 | 554 | pthread_cleanup_push(cleanup_handler, (void *)&control); 555 | 556 | status = pthread_mutex_lock(&control.mutex); 557 | if (status != 0) 558 | err_abort(status, "Mutex lock"); 559 | 560 | control.counter++; 561 | 562 | while (control.bysy) 563 | { 564 | status = pthread_cond_wait(&control.cv, &control.mutex); 565 | if (status != 0) 566 | err_abort(status, "Wait on condition"); 567 | } 568 | 569 | pthread_cleanup_pop(1); 570 | return NULL; 571 | } 572 | 573 | int main() 574 | { 575 | pthread_t thread_id[THREADS]; 576 | int count; 577 | void *result; 578 | int status; 579 | 580 | for (count = 0; count < THREADS; count++) 581 | { 582 | status = pthread_create(&thread_id[count], NULL, thread_routine, NULL); 583 | if (status != 0) 584 | err_abort(status, "Create thread"); 585 | } 586 | 587 | sleep(2); 588 | 589 | for (count = 0; count < THREADS; count++) 590 | { 591 | status = pthread_cancel(thread_id[count]); 592 | if (status != 0) 593 | err_abort(status, "Cancel thread"); 594 | 595 | status = pthread_join(thread_id[count], &result); 596 | if (status != 0) 597 | err_abort(status, "Join thread"); 598 | 599 | if (result == PTHREAD_CANCELED) 600 | printf("Thread %d canceled\n", count); 601 | else 602 | printf("Thread %d was not canceled\n", count); 603 | } 604 | } 605 | ``` 606 | 607 | 如果你的一个线程创建了一套线程来“转包”一些功能(如并行算术运算),并且当分包线程在进行中时“承包线程”被取消,你可能不希望留着分包线程继续运行。相反,可以把取消操作“传递”到每个“分包线程”,让它们独立地处理自己的终止过程。当“承包线程”取消它们时,不应该连接分包线程来推迟取消,相反,可以取消每个线程并且使用 `pthread_detach` 很快地分离它。当它们完成时,分包线程的资源就能够很快被重用, 而“承包线程” 同时能独立地完成一些事情。 608 | 609 | 以下程序演示了同时独立取消“分包线程”的一个方法: 610 | 611 | ```c 612 | #include 613 | #include "errors.h" 614 | 615 | #define THREADS 5 616 | 617 | typedef struct team_tag { 618 | int join_i; 619 | pthread_t workers[THREADS]; 620 | } team_t; 621 | 622 | void *worker_routine(void *arg) 623 | { 624 | int counter; 625 | 626 | for(counter = 0; ; counter++) 627 | if ((counter % 1000) == 0) 628 | pthread_testcancel(); 629 | } 630 | 631 | void cleanup(void *arg) 632 | { 633 | team_t *team = (team_t *)arg; 634 | int count, status; 635 | 636 | for (count = team->join_i; count < THREADS; count++) { 637 | status = pthread_cancel(team->workers[count]); 638 | if (status != 0) 639 | err_abort(status, "Cancel worker"); 640 | 641 | status = pthread_detach(team->workers[count]); 642 | if (status != 0) 643 | err_abort(status, "Detach worker"); 644 | 645 | printf("Cleanup: canceled %d\n", count); 646 | } 647 | } 648 | 649 | void *thread_routine(void *arg) 650 | { 651 | team_t team; 652 | int count; 653 | void *result; 654 | int status; 655 | 656 | for (count = 0; count < THREADS; count++) { 657 | status = pthread_create(&team.workers[count], NULL, worker_routine, NULL); 658 | if (status != 0) 659 | err_abort(status, "Create worker"); 660 | } 661 | 662 | pthread_cleanup_push(cleanup, (void *)&team); 663 | 664 | for (team.join_i = 0; team.join_i < THREADS; team.join_i++) { 665 | status = pthread_join(team.workers[team.join_i], &result); 666 | if (status != 0) 667 | err_abort(status, "Join worker"); 668 | } 669 | 670 | pthread_cleanup_pop(1); 671 | return NULL; 672 | } 673 | 674 | int main() 675 | { 676 | pthread_t thread_id; 677 | int status; 678 | 679 | status = pthread_create(&thread_id, NULL, thread_routine, NULL); 680 | if (status != 0) 681 | err_abort(status, "Create team"); 682 | 683 | sleep(5); 684 | 685 | status = pthread_cancel(thread_id); 686 | if (status != 0) 687 | err_abort(status, "Cancel team"); 688 | 689 | status = pthread_join(thread_id, NULL); 690 | if (status != 0) 691 | err_abort(status, "Join team"); 692 | 693 | return 0; 694 | } 695 | ``` 696 | 697 | ## 线程私有数据 698 | 699 | 在进程内的所有线程共享相同的地址空间,即意味着任何声明为静态或外部的变量,或在进程堆声明的变量,都可以被进 700 | 程内所有的线程读写。 701 | 702 | 当线程需要一个私有变量时,必须首先决定所有的线程是否共享相同的值,或者线程是否应该有它自己的值。如果它们共享变量,则可以使用静态或外部数据,就像你能在一个单线程程序做的那样;然而,必须同步跨越多线程对共享数据的存取, 运通常通过增加一个或多个互斤量来完成。 703 | 704 | 如果每个线程都需要一个私有变量值,则必须在某处存储所有的值。线程私有数据允许每个线程保有一份变量的拷贝,好像每个线程有一连串通过公共的“键”值索引的私有数据值。 705 | 706 | ### 建立和使用线程私有数据 707 | 708 | 线程私有数据键在程序中是由类型 `pthread_key_t` 来表示的。 709 | 710 | 在任何线程试图使用键以前,创建线程私有数据键最容易的方法是调用 `pthread_key_create`,但必须保证 `pthread_key_create` 对于每个 `pthread_key_t` 变童仅仅调用一次。如果将一个键创建两次,其实是在创建两个不同的键。第二个键将覆盖第一个,第一个键与任何线程为其设置的值一起将永远地丢失。所以,最容易一次性创建一个键的方法是使用 `pthread_once`。 711 | 712 | 当程序不再需要时,你可以调用 `pthread_key_delete` 释放一个线程私有数据键。 713 | 714 | 在私有数据创建后,你可以使用 `pthread_getspecific` 函数来获得线程当前的键值,或调用 `pthread_setspecific` 来改变当前的键值。 715 | 716 | ```c 717 | pthread_key_t key; 718 | int pthread_key_create(pthread_key_t *key, void (*destructor)(void *)); 719 | int pthread_key_delete(pthread_key_t key); 720 | void pthread_setspecific(pthread_key_t key, const void *value); 721 | void *pthread_getspecific(pthread_key_t); 722 | ``` 723 | 724 | 下面是一个建立和使用线程私有数据的实例: 725 | 726 | ```c 727 | #include 728 | #include "errors.h" 729 | 730 | typedef struct tsd_tag { 731 | pthread_t thread_id; 732 | char *string; 733 | } tsd_t; 734 | 735 | pthread_key_t tsd_key; 736 | pthread_once_t key_one = PTHREAD_ONCE_INIT; 737 | 738 | void once_routine(void) 739 | { 740 | int status; 741 | 742 | printf("initializing key\n"); 743 | status = pthread_key_create(&tsd_key, NULL); 744 | if (status != 0) 745 | err_abort(status, "Create key"); 746 | } 747 | 748 | void *thread_routine(void *arg) 749 | { 750 | tsd_t *value; 751 | int status; 752 | 753 | status = pthread_once(&key_one, once_routine); 754 | if (status != 0) 755 | err_abort(status, "Once init"); 756 | 757 | value = (tsd_t*)malloc(sizeof(tsd_t)); 758 | if (value == NULL) 759 | errno_abort("Allocate key value"); 760 | 761 | status = pthread_setspecific(tsd_key, value); 762 | if (status != 0) 763 | err_abort(status, "Set tsd"); 764 | 765 | printf("%s set tsd value %p\n", (char*)arg, value); 766 | 767 | value->thread_id = pthread_self(); 768 | value->string = (char*)arg; 769 | 770 | value = (tsd_t*)pthread_getspecific(tsd_key); 771 | printf("%s starting...\n", value->string); 772 | 773 | sleep(2); 774 | 775 | value = (tsd_t*)pthread_getspecific(tsd_key); 776 | printf("%s done...\n", value->string); 777 | 778 | return NULL; 779 | } 780 | 781 | int main() 782 | { 783 | int status; 784 | pthread_t thread1, thread2; 785 | 786 | status = pthread_create(&thread1, NULL, thread_routine, "thread 1"); 787 | if (status != 0) 788 | err_abort(status, "Create thread 1"); 789 | 790 | status = pthread_create(&thread2, NULL, thread_routine, "thread 2"); 791 | if (status != 0) 792 | err_abort(status, "Create thread 2"); 793 | 794 | pthread_exit(NULL); 795 | } 796 | ``` 797 | 798 | ### 使用 destructor 函数 799 | 800 | 当一个线程退出时,它有一些为线程私有数据键定义的值,通常需要处理它们。当你创建一个线程私有数据键时,Pthreads 允许你定义 `destructor` 函数。当具有非空的私有数据键值的一个线程终止时,键的 `destructor` (如果存在) 将以键的当前值为参数被调用。 801 | 802 | 下列程序表明了当一个线程终止时使用线程私有数据的 `destructors` 释放存储器。 它还跟踪有多少线程正在使用线程私有数据, 并且当最后线程的 `destructor` 被调用时, 删除线程私有数据键。 803 | 804 | ```c 805 | #include 806 | #include "errors.h" 807 | 808 | typedef struct private_tag { 809 | pthread_t thread_id; 810 | char *string; 811 | } private_t; 812 | 813 | pthread_key_t identity_key; 814 | pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER; 815 | long identity_key_counter = 0; 816 | 817 | void identity_key_destructor(void *value) 818 | { 819 | private_t *private = (private_t*)value; 820 | int status; 821 | 822 | printf("thread \"%s\" exiting...\n", private->string); 823 | free(value); 824 | 825 | status = pthread_mutex_lock(&identity_key_mutex); 826 | if (status != 0) 827 | err_abort(status, "Lock key mutex"); 828 | 829 | identity_key_counter--; 830 | if (identity_key_counter <= 0) { 831 | status = pthread_key_delete(identity_key); 832 | if (status != 0) 833 | err_abort(status, "Delete key"); 834 | printf("key delete...\n"); 835 | } 836 | 837 | status = pthread_mutex_unlock(&identity_key_mutex); 838 | if (status != 0) 839 | err_abort(status, "Unlock key mutex"); 840 | } 841 | 842 | void *identity_key_get(void) 843 | { 844 | void *value; 845 | int status; 846 | 847 | value = pthread_getspecific(identity_key); 848 | if (value == NULL) { 849 | value = malloc(sizeof(private_t)); 850 | if (value == NULL) 851 | errno_abort("Allocate key value"); 852 | 853 | status = pthread_setspecific(identity_key, value); 854 | if (status != 0) 855 | err_abort(status, "Set TSD"); 856 | } 857 | 858 | return value; 859 | } 860 | 861 | void *thread_routine(void *arg) 862 | { 863 | private_t *value; 864 | 865 | value = (private_t*) identity_key_get(); 866 | value->thread_id = pthread_self(); 867 | value->string = (char*)arg; 868 | printf("thread \"%s\" starting...\n", value->string); 869 | sleep(2); 870 | return NULL; 871 | } 872 | 873 | int main() 874 | { 875 | pthread_t thread_1, thread_2; 876 | private_t *value; 877 | int status; 878 | 879 | status = pthread_key_create(&identity_key, identity_key_destructor); 880 | if (status != 0) 881 | err_abort(status, "Create key"); 882 | 883 | identity_key_counter = 3; 884 | 885 | value = (private_t*)identity_key_get(); 886 | value->thread_id = pthread_self(); 887 | value->string = "Main thread"; 888 | 889 | status = pthread_create(&thread_1, NULL, thread_routine, "thread 1"); 890 | if (status != 0) 891 | err_abort(status, "Create thread 1"); 892 | 893 | status = pthread_create(&thread_2, NULL, thread_routine, "thread 2"); 894 | if (status != 0) 895 | err_abort(status, "Create thread 2"); 896 | 897 | pthread_exit(NULL); 898 | } 899 | ``` 900 | 901 | ## 线程实时调度 902 | 903 | “受限制”的响应时间不一定是“快”的反应,而是确实意味着“可预知”的响应速度。必须有一些方法来定义一个时间跨度,在该时间段内一系列操作保证被完成。例如控制一个核反应堆的系统比你将写的大多数程序有更严格的响应要求,并且没能满足反应堆要求的后果是更严重的。 904 | 905 | 很多代码将需要在“确定的反应时间”内提供一些“达到要求水平的服务”,我们称为实时编程。 906 | 907 | 实时编程分为“硬实时”和“软实时”。“硬实时”是不可原谅的,如燃料干的调整被推迟几微妙,你的核反应堆将会很危险;“软实时”意味着你大部分时间需要满足调度要求,但是如果不能能满足,后果也不是很严重。 908 | 909 | ### POSIX 实时选项 910 | 911 | 优先级调度允许程序员给系统提供了任何两个线程间相。无论何时当多个线程准备好执行时,系统将选择最高优先级的线程。 912 | 913 | #### 调度策略和优先级。 914 | 915 | 调度策略允许设置各个调度策略的最小和最大优先级。POXIS 标准提供两种调度策略(`SCHED_FIFO` 和 `SCHED_RR`)。 916 | 917 | - `SCHED_FIFO`(先入先出)策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。 918 | - `SCHED_RR`(轮循),和先入先出策略是基本相同的,不同之处在于:如果有一个 `SCHED_RR`策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的`SCHED_RR` 或 `SCHED_FIFO` 策略的相同优先级的线程准备好时,运行的线程将被抢占以使准备好的线程可以执行。 919 | 920 | 程序 `sched_attr.c` 显示了如何使用属性对象来创建一个具有显式的调度策略和优先级的线程。 921 | 程序 `sched_thread.c` 显示了如何为一个正在运行的线程修改实时调度策略和参数。 922 | 923 | #### 竞争范围和分配域。 924 | 925 | 如果你正在写一个实时的应用程序,应该知道系统对这些控制量设置的支持,否则它们可能没有什么关系。 926 | 927 | - **竞争范围**,它描述了线程为处理器资源而竞争的方式。系统竞争范围意味着线程与进程之外的线程竞争处理器资源。一个进程内的髙优先级系统竞争范围线程能阻止其他进程内的系统竞争范围线程运行。进程竞争范围指线程仅仅在同一进程内相互竞争。可以使用 `pthread_attr_setscope` 设置竞争范围。 928 | - **分配域**,分配域是系统内线程可以为其竞争的处理器的集合。一个系统叫以有一个以上的分配领域,每个包含一个以上的处理器。在一个单处理机系统内,分配域将只包含一个处理器,但是你仍然可以有多个分配域。在一台多处理机上,各个分配领域可以包含从一个处理器到系统中所有的处理器。 929 | --------------------------------------------------------------------------------