线程同步,指的是控制多线程间的相对执行顺序,从而在线程间正确、有序地共享数据,以下为线程同步常见使用场合。
在实际项目中,经常使用的线程同步方法主要分为三种:
本节内容只介绍互斥锁和条件变量,Posix信号量后续在Posix IPC专题中介绍。
互斥锁用于确保同一时间只有一个线程访问共享数据,使用方法为:
对互斥锁加锁后,任何其他试图再次对其加锁的线程都会被阻塞,直到当前线程释放该互斥锁,解锁时所有阻塞线程都会变成可运行状态,但究竟哪个先运行,这一点是不确定的。
互斥锁是用pthread_mutex_t数据类型表示的,在使用互斥锁之前,需要先进行初始化,初始化方法有两种:
PTHREAD_MUTEX_INITIALIZER,只适用于静态分配的互斥锁pthread_mutex_init函数,静态分配和动态分配的互斥锁都可以互斥锁使用完以后,可以调用pthread_mutex_destroy进行销毁,尤其是对于动态分配的互斥锁,在释放内存前,调用pthread_mutex_destroy是必须的。
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
- //两个函数的返回值:成功返回0,失败返回错误编号
- int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
- int pthread_mutex_destroy(pthread_mutex_t *mutex);
其中,pthread_mutex_init的第二个参数attr用于设置互斥锁的属性,如果要使用默认属性,只需把attr设为NULL。
- //两个函数的返回值:成功返回0,失败返回错误编号
- int pthread_mutex_lock(pthread_mutex_t *mutex);
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
对互斥锁上锁,需要调用pthread_mutex_lock,如果互斥锁已经上锁,调用线程将阻塞到该互斥锁被释放。
对互斥锁解锁,需要调用pthread_mutex_unlock。
- //成功返回0,失败返回错误编号
- int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果不希望调用线程阻塞,可以使用pthread_mutex_trylock尝试上锁:
EBUSY- //成功返回0,失败返回错误编号
- int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *time);
pthread_mutex_timedlock是一个可以设置阻塞时间的上锁函数:
ETIMEDOUT关于第二个参数time,有两点需要注意:
- /*
- * 测试使用上述4个加锁函数
- */
-
- #include <pthread.h>
- #include <time.h>
- #include <errno.h>
- #include <stdio.h>
-
- pthread_mutex_t mutex1;
- pthread_mutex_t mutex2;
- pthread_mutex_t mutex3;
-
- void *thread1_start(void *arg)
- {
- pthread_mutex_lock(&mutex1);
- printf("thread1 has locked mutex1\n");
- sleep(2); //保证thread2执行时mutex1还未解锁
- pthread_mutex_unlock(&mutex1);
- }
-
- void *thread2_start(void *arg)
- {
- if (pthread_mutex_trylock(&mutex2) == 0)
- printf("thread2 trylock mutex2 sucess\n");
-
- if (pthread_mutex_trylock(&mutex1) == EBUSY)
- printf("thread2 trylock mutex1 failed\n");
-
- pthread_mutex_unlock(&mutex2);
- }
-
- void *thread3_start(void *arg)
- {
- struct timespec time;
- struct tm *tmp_time;
- char s[64];
- int err;
-
- pthread_mutex_lock(&mutex3);
- printf("thread3 has locked mutex3\n");
-
- /*获取当前时间,并转化为本地时间打印*/
- clock_gettime(CLOCK_REALTIME, &time);
- tmp_time = localtime(&time.tv_sec);
- strftime(s, sizeof(s), "%r", tmp_time);
- printf("current time is %s\n", s);
-
- /*设置time = 当前时间 + 等待时间10S*/
- time.tv_sec = time.tv_sec + 10;
-
- /*mutex3已上锁,这里会阻塞*/
- if (pthread_mutex_timedlock(&mutex3, &time) == ETIMEDOUT)
- printf("pthread_mutex_timedlock mutex3 timeout\n");
-
- /*再次获取当前时间,并转化为本地时间打印*/
- clock_gettime(CLOCK_REALTIME, &time);
- tmp_time = localtime(&time.tv_sec);
- strftime(s, sizeof(s), "%r", tmp_time);
- printf("the time is now %s\n", s);
-
- pthread_mutex_unlock(&mutex3);
- }
-
- int main()
- {
- pthread_t tid1;
- pthread_t tid2;
- pthread_t tid3;
-
- /*测试pthread_mutex_lock和pthread_mutex_trylock*/
- pthread_mutex_init(&mutex1, NULL);
- pthread_mutex_init(&mutex2, NULL);
-
- pthread_create(&tid1, NULL, thread1_start, NULL);
- pthread_create(&tid2, NULL, thread2_start, NULL);
-
- if (pthread_join(tid1, NULL) == 0)
- {
- pthread_mutex_destroy(&mutex1);
- }
-
- if (pthread_join(tid2, NULL) == 0)
- {
- pthread_mutex_destroy(&mutex2);
- }
-
- /*测试pthread_mutex_timedlock*/
- pthread_mutex_init(&mutex3, NULL);
- pthread_create(&tid3, NULL, thread3_start, NULL);
-
- if (pthread_join(tid3, NULL) == 0)
- {
- pthread_mutex_destroy(&mutex3);
- }
-
- return 0;
- }

线程间死锁,指的是线程间相互等待临界资源而造成彼此无法继续执行的现象。

直观上看,循环等待条件似乎和死锁的定义一样,其实不然,因为死锁定义中的要求更为严格:
条件变量是用pthread_cond_t数据类型表示的,和互斥锁类似,条件变量的初始化方法也有两种:
PTHREAD_COND_INITIALIZER,只适用于静态分配的条件变量pthread_cond_init函数,适用于静态分配和动态分配的条件变量条件变量使用完以后,可以调用pthread_cond_destroy进行销毁,同样的,如果是动态分配的条件变量,在释放内存前,该操作也是必须的。
- pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
-
- //两个函数的返回值:成功返回0,失败返回错误编号
- int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
- int pthread_cond_destroy(pthread_cond_t *cond);
其中,pthread_cond_init的第二个参数attr用于设置条件变量的属性,如果要使用默认属性,只需把attr设为NULL。
- //两个函数的返回值:成功返回0,失败返回错误编号
- int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *timeout);
可以调用pthread_cond_wait函数等待条件满足,使用步骤如下,传递给函数的互斥锁对条件进行保护,在条件满足之前,调用线程将一直阻塞。
pthread_cond_timedwait是一个限时等待条件满足的函数,如果发生超时时条件还没满足,pthread_cond_timedwait将重新对互斥锁上锁,然后返回ETIMEDOUT错误。
注意:当条件满足从pthread_cond_wait和pthread_cond_timedwait返回时,调用线程必须重新计算条件,因为另一个线程可能已经在运行并改变了条件。
有两个函数可以用于通知线程条件已经满足:
pthread_cond_signal至少能唤醒一个等待该条件的线程pthread_cond_broadcast可以唤醒等待该条件的所有线程- //两个函数的返回值:成功返回0,失败返回错误编号
- int pthread_cond_signal(pthread_cond_t *cond);
- int pthread_cond_broadcast(pthread_cond_t *cond);
在调用上面两个函数时,我们说这是在给线程发信号,注意,一定要先获取互斥锁,再改变条件,然后给线程发信号,最后再对互斥锁解锁。
- /*
- * 结合使用条件变量和互斥锁进行线程同步
- */
-
- #include <pthread.h>
- #include <stdio.h>
-
- static pthread_cond_t cond;
- static pthread_mutex_t mutex;
- static int cond_value;
- static int quit;
-
- void *thread_signal(void *arg)
- {
- while (!quit)
- {
- pthread_mutex_lock(&mutex);
- cond_value++; //改变条件,使条件满足
- pthread_cond_signal(&cond); //给线程发信号
- printf("signal send, cond_value: %d\n", cond_value);
- pthread_mutex_unlock(&mutex);
- sleep(1);
- }
- }
-
- void *thread_wait(void *arg)
- {
- while (!quit)
- {
- pthread_mutex_lock(&mutex);
-
- /*通过while (cond is true)来保证从pthread_cond_wait成功返回时,调用线程会重新检查条件*/
- while (cond_value == 0)
- pthread_cond_wait(&cond, &mutex);
-
- cond_value--;
- printf("signal recv, cond_value: %d\n", cond_value);
-
- pthread_mutex_unlock(&mutex);
- sleep(1);
- }
- }
-
- int main()
- {
- pthread_t tid1;
- pthread_t tid2;
-
- pthread_cond_init(&cond, NULL);
- pthread_mutex_init(&mutex, NULL);
-
- pthread_create(&tid1, NULL, thread_signal, NULL);
- pthread_create(&tid2, NULL, thread_wait, NULL);
-
- sleep(5);
- quit = 1;
-
- pthread_join(tid1, NULL);
- pthread_join(tid2, NULL);
-
- pthread_cond_destroy(&cond);
- pthread_mutex_destroy(&mutex);
-
- return 0;
- }
