互斥量
使用互斥量可以确保同一时间只有一个线程访问数据。
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后解锁互斥量。
对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。
相关函数
初始化和销毁
#include <pthread.h>
/* 静态分配互斥量 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/* 动态分配互斥量 */
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
/* 动态销毁互斥量 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);
加锁和解锁
#include <pthread.h>
/* 加锁 */
int pthread_mutex_lock(pthread_mutex_t *mutex);
/* 试图加锁 */
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/* 解锁 */
int pthread_mutex_unlock(pthread_mutex_t *mutex);
代码示例一(多个线程读写一个文件)
向 /tmp/test写入1,创建20个线程读该文件内容并将读到的值加1,然后写入。
期望最终的文件内容为21。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define CONFIG_TEST_FILE_PATH "/tmp/test" /* 文件路径 */
#define CONFIG_TEST_THREAD_CNT 20 /* 读写线程数 */
#define CONFIG_TEST_BUFF_SIZE 1024 /* 读取缓存大小 */
/* 数组元素数量 */
#define ELEMENT_SIZE(ARRAY) (sizeof(ARRAY)/(sizeof(ARRAY[0])))
/* 线程对象 */
typedef struct pthread_obj {
char *p_buff;
int size;
pthread_t tid;
} pthread_obj_t;
/* 线程对象数组 */
static pthread_obj_t pthread_pool[CONFIG_TEST_THREAD_CNT];
/* 初始化 */
static int pthread_pool_init(void) {
int i = 0;
int j = 0;
pthread_obj_t *p_pthread = NULL;
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
p_pthread->p_buff = malloc(CONFIG_TEST_BUFF_SIZE * sizeof(char));
if (p_pthread->p_buff == NULL) {
j = i;
goto clean;
}
p_pthread->size = CONFIG_TEST_BUFF_SIZE * sizeof(char);
}
return 0;
clean:
for (i=0; i<j; i++) {
p_pthread = &pthread_pool[i];
free(p_pthread->p_buff);
}
return -1;
}
/* 解初始化 */
static void pthread_pool_deinit(void) {
int i = 0;
pthread_obj_t *p_pthread = NULL;
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
free(p_pthread->p_buff);
p_pthread->p_buff = NULL;
p_pthread->size = 0;
}
}
/* 线程执行函数 */
static void *thread_handle(void *p_arg) {
pthread_obj_t *p_pthread = (pthread_obj_t *)p_arg;
FILE *fp = NULL;
fp = fopen(CONFIG_TEST_FILE_PATH, "r+");
if (fp == NULL) {
perror("fopen()");
exit(-1);
}
fgets(p_pthread->p_buff, p_pthread->size, fp);
fseek(fp, 0, SEEK_SET); /* 重置文件指针 */
sleep(1);
fprintf(fp, "%d\n", atoi(p_pthread->p_buff) + 1); /* 写入内容 */
fclose(fp);
pthread_exit(NULL);
}
int main(void) {
int i = 0;
int err = 0;
pthread_obj_t *p_pthread = NULL;
if (pthread_pool_init() < 0) {
printf("pthread pool init failed\r\n");
exit(-1);
}
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
err = pthread_create(&p_pthread->tid, NULL, thread_handle, (void *)p_pthread);
if (err) {
fprintf(stderr, "pthread_create(): %s\n", strerror(err));
exit(-1);
}
}
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
pthread_join(p_pthread->tid, NULL);
}
pthread_pool_deinit();
exit(0);
}
执行结果
root@who-virtual-machine:~/linux/APUE/pthread$ echo 1 > /tmp/test
root@who-virtual-machine:~/linux/APUE/pthread$ cat /tmp/test
1
root@who-virtual-machine:~/linux/APUE/pthread$ ./test1
root@who-virtual-machine:~/linux/APUE/pthread$ cat /tmp/test
2
原因分析:
由于系统调度和竞争,线程读到文件内容1后,休眠1s,然后一起写入2,所以结果就为2
代码示例二(使用互斥量实现多线程读写一个文件)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define CONFIG_TEST_FILE_PATH "/tmp/test" /* 文件路径 */
#define CONFIG_TEST_THREAD_CNT 20 /* 读写线程数 */
#define CONFIG_TEST_BUFF_SIZE 1024 /* 读取缓存大小 */
/* 数组元素数量 */
#define ELEMENT_SIZE(ARRAY) (sizeof(ARRAY)/(sizeof(ARRAY[0])))
/* 线程对象 */
typedef struct pthread_obj {
char *p_buff;
int size;
pthread_t tid;
} pthread_obj_t;
/* 线程对象数组 */
static pthread_obj_t pthread_pool[CONFIG_TEST_THREAD_CNT];
/* 文件互斥锁 */
static pthread_mutex_t file_mutex;
/* 初始化 */
static int pthread_pool_init(void) {
int i = 0;
int j = 0;
pthread_obj_t *p_pthread = NULL;
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
p_pthread->p_buff = malloc(CONFIG_TEST_BUFF_SIZE * sizeof(char));
if (p_pthread->p_buff == NULL) {
j = i;
goto clean;
}
p_pthread->size = CONFIG_TEST_BUFF_SIZE * sizeof(char);
}
return 0;
clean:
for (i=0; i<j; i++) {
p_pthread = &pthread_pool[i];
free(p_pthread->p_buff);
}
return -1;
}
/* 解初始化 */
static void pthread_pool_deinit(void) {
int i = 0;
pthread_obj_t *p_pthread = NULL;
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
free(p_pthread->p_buff);
p_pthread->p_buff = NULL;
p_pthread->size = 0;
}
}
/* 线程执行函数 */
static void *thread_handle(void *p_arg) {
pthread_obj_t *p_pthread = (pthread_obj_t *)p_arg;
FILE *fp = NULL;
fp = fopen(CONFIG_TEST_FILE_PATH, "r+");
if (fp == NULL) {
perror("fopen()");
exit(-1);
}
pthread_mutex_lock(&file_mutex); /* 临界区加锁 */
fgets(p_pthread->p_buff, p_pthread->size, fp);
fseek(fp, 0, SEEK_SET); /* 重置文件指针 */
fprintf(fp, "%d\n", atoi(p_pthread->p_buff) + 1); /* 写入内容 */
fclose(fp);
pthread_mutex_unlock(&file_mutex); /* 退出临界区解锁 */
sleep(1);
pthread_exit(NULL);
}
int main(void) {
int i = 0;
int err = 0;
pthread_obj_t *p_pthread = NULL;
if (pthread_pool_init() < 0) {
printf("pthread pool init failed\r\n");
exit(-1);
}
pthread_mutex_init(&file_mutex, NULL);
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
err = pthread_create(&p_pthread->tid, NULL, thread_handle, (void *)p_pthread);
if (err) {
fprintf(stderr, "pthread_create(): %s\n", strerror(err));
exit(-1);
}
}
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
pthread_join(p_pthread->tid, NULL);
}
pthread_pool_deinit();
pthread_mutex_destroy(&file_mutex);
exit(0);
}
执行结果
root@who-virtual-machine:~/linux/APUE/pthread$ echo 1 > /tmp/test
root@who-virtual-machine:~/linux/APUE/pthread$ cat /tmp/test
1
root@who-virtual-machine:~/linux/APUE/pthread$ ./test2
root@who-virtual-machine:~/linux/APUE/pthread$ cat /tmp/test
21
代码示例三(使用互斥量实现线程同步)
实现十个线程在终端依次打印0123456789
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define CONFIG_TEST_THREAD_CNT 10 /* 读写线程数 */
/* 数组元素数量 */
#define ELEMENT_SIZE(ARRAY) (sizeof(ARRAY)/(sizeof(ARRAY[0])))
/* 打印范围 */
#define CONFIG_TEST_PRINTF_NUM_MIN 0
#define CONFIG_TEST_PRINTF_NUM_MAX 9
/* 线程对象 */
typedef struct pthread_obj {
pthread_t tid;
} pthread_obj_t;
/* 线程对象数组 */
static pthread_obj_t pthread_pool[CONFIG_TEST_THREAD_CNT];
/* 打印值 */
static int value;
/* 互斥锁 */
static pthread_mutex_t mutex;
/* 初始化 */
static void pthread_value_init(void) {
value = CONFIG_TEST_PRINTF_NUM_MIN;
}
/* 取值 */
static int pthread_value_get(void) {
int result;
result = value;
if (value >= CONFIG_TEST_PRINTF_NUM_MAX) {
value = CONFIG_TEST_PRINTF_NUM_MIN;
} else {
value++;
}
return result;
}
/* 线程执行函数 */
static void *thread_handle(void *p_arg) {
int c = 0;
while (1) {
pthread_mutex_lock(&mutex);
c = pthread_value_get();
c = c + '0';
write(1, &c, 1);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(void) {
int i = 0;
int err = 0;
pthread_obj_t *p_pthread = NULL;
pthread_value_init();
pthread_mutex_init(&mutex, NULL);
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
err = pthread_create(&p_pthread->tid, NULL, thread_handle, NULL);
if (err) {
fprintf(stderr, "pthread_create(): %s\n", strerror(err));
exit(-1);
}
}
/* 运行3s终止进程 */
alarm(3);
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
pthread_join(p_pthread->tid, NULL);
}
pthread_mutex_destroy(&mutex);
exit(0);
}
代码示例四(线程池)
线程数是有数量限制的,可以通过shell命令 ulimit -a 查看支持的最大线程数量。
利用线程池来检测质数,假设线程池提供4个线程来检测300000~3000200范围内所有质数(共201个质数)。
设置临界区资源 value, value取值如下:
value = 3000000~30000200:临界区资源下发,需要有一个线程来接收并执行任务。设置临界区状态 status, status取值如下:
status = 0 :当前没有临界区资源下发,没有任务执行。
status = 1 :没有临界区资源,可以下发。
status = -1:临界区资源下发已全部完成,结束任务。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <linux/types.h>
#ifndef false
#define false (0)
#endif
#ifndef true
#define true (!false)
#endif
/* 线程数 */
#define CONFIG_TEST_THREAD_CNT 4
/* 数组元素数量 */
#define ELEMENT_SIZE(ARRAY) (sizeof(ARRAY)/(sizeof(ARRAY[0])))
/* 要寻找的质数起始值 */
#define CONFIG_TEST_PRIMER_START 30000000
/* 要寻找的质数结束值 */
#define CONFIG_TEST_PRIMER_END 30000200
/* 分发状态 */
#define CONFIG_TEST_EMIT_FINISH (-1)
#define CONFIG_TEST_EMIT_STOP 0
#define CONFIG_TEST_EMIT_START 1
/* 线程对象结构体 */
typedef struct pthread_obj {
int value;
int running;
int index;
pthread_t tid;
} pthread_obj_t;
/* 临界区资源 */
typedef struct emit_obj {
int value;
int status;
pthread_mutex_t mutex;
} emit_obj_t;
/* 线程对象数组 */
static pthread_obj_t pthread_pool[CONFIG_TEST_THREAD_CNT];
/* 临界区资源 */
static emit_obj_t emit_source;
/* 初始化 */
static int pthread_pool_init(void) {
int i = 0;
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
pthread_pool[i].value = 0;
pthread_pool[i].running = true;
pthread_pool[i].index = 0;
}
return 1;
}
/* 解初始化 */
static void pthread_pool_deinit(void) {
int i = 0;
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
pthread_pool[i].value = 0;
pthread_pool[i].running = false;
pthread_pool[i].index = 0;
}
}
static int emit_source_init(void) {
emit_obj_t *p_emit = (emit_obj_t *)&emit_source;
pthread_mutex_init(&p_emit->mutex, NULL);
p_emit->value = CONFIG_TEST_PRIMER_START;
p_emit->status = CONFIG_TEST_EMIT_STOP;
return 1;
}
static void emit_source_deinit(void) {
emit_obj_t *p_emit = (emit_obj_t *)&emit_source;
pthread_mutex_destroy(&p_emit->mutex);
p_emit->value = 0;
p_emit->status = CONFIG_TEST_EMIT_FINISH;
}
static int emit_source_get(void) {
emit_obj_t *p_emit = (emit_obj_t *)&emit_source;
int res;
pthread_mutex_lock(&p_emit->mutex);
if (p_emit->status == CONFIG_TEST_EMIT_STOP || p_emit->status == CONFIG_TEST_EMIT_FINISH) {
res = -1;
} else {
res = p_emit->value;
p_emit->status = CONFIG_TEST_EMIT_STOP;
}
pthread_mutex_unlock(&p_emit->mutex);
return res;
}
static int emit_source_set(void) {
emit_obj_t *p_emit = (emit_obj_t *)&emit_source;
pthread_mutex_lock(&p_emit->mutex);
if (p_emit->value > CONFIG_TEST_PRIMER_END) {
p_emit->status = CONFIG_TEST_EMIT_FINISH;
} else {
if (p_emit->status == CONFIG_TEST_EMIT_STOP) {
p_emit->value++;
p_emit->status = CONFIG_TEST_EMIT_START;
}
}
pthread_mutex_unlock(&p_emit->mutex);
return 1;
}
static int emit_source_is_null(void) {
emit_obj_t *p_emit = (emit_obj_t *)&emit_source;
int res = 0;
pthread_mutex_lock(&p_emit->mutex);
if (p_emit->status == CONFIG_TEST_EMIT_STOP) {
res = 1;
}
pthread_mutex_unlock(&p_emit->mutex);
return res;
}
static int emit_source_is_finish(void) {
emit_obj_t *p_emit = (emit_obj_t *)&emit_source;
int res = 0;
pthread_mutex_lock(&p_emit->mutex);
if (p_emit->status == CONFIG_TEST_EMIT_FINISH) {
res = 1;
}
pthread_mutex_unlock(&p_emit->mutex);
return res;
}
static int IsPrime(int num)
{
for (int i = 2; i < (num / 2); i++) {
if (num % i == 0) {
return 0;
}
}
return 1;
}
static void *thread_handle(void *p_arg) {
pthread_obj_t *p_pthread = (pthread_obj_t *)p_arg;
int i = 0;
int j = 0;
while (p_pthread->running == true) {
__start:
if (emit_source_is_finish()) {
p_pthread->running = false;
continue;
}
if (emit_source_is_null()) {
sched_yield();
} else {
p_pthread->value = emit_source_get();
if (p_pthread->value < 0) {
goto __start;
}
if (IsPrime(p_pthread->value)) {
printf("[thread-%d]%d is a primer.\n", p_pthread->index, p_pthread->value);
}
}
}
pthread_exit(NULL); /* 线程退出 */
}
int main(void) {
int i = 0;
int err = 0;
pthread_obj_t *p_pthread = NULL;
pthread_pool_init();
emit_source_init();
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
p_pthread->index = i;
err = pthread_create(&p_pthread->tid, NULL, thread_handle, (void *)p_pthread);
if (err) {
fprintf(stderr, "pthread create failed: %s\r\n", strerror(err));
exit(-1);
}
}
while (!emit_source_is_finish()) {
if (!emit_source_is_null()) {
sched_yield();
} else {
emit_source_set();
}
}
for (i=0; i<ELEMENT_SIZE(pthread_pool); i++) {
p_pthread = &pthread_pool[i];
pthread_join(p_pthread->tid, NULL);
}
pthread_pool_deinit();
emit_source_deinit();
exit(0);
}
执行结果
[thread-1]30000001 is a primer.
[thread-3]30000037 is a primer.
[thread-0]30000023 is a primer.
[thread-2]30000041 is a primer.
[thread-2]30000071 is a primer.
[thread-1]30000049 is a primer.
[thread-2]30000083 is a primer.
[thread-0]30000079 is a primer.
[thread-3]30000059 is a primer.
[thread-1]30000109 is a primer.
[thread-2]30000133 is a primer.
[thread-0]30000137 is a primer.
[thread-1]30000149 is a primer.
[thread-2]30000167 is a primer.
[thread-0]30000169 is a primer.
[thread-3]30000163 is a primer.
[thread-1]30000193 is a primer.
[thread-2]30000199 is a primer.