线程同步(同步的意思是协同步调)
线程同步机制包括互斥,读写锁以及条件变量等
3.2.1 互斥量(互斥锁)
**互斥量本质是一把锁,在访问公共资源前对互斥量设置(加锁),确保同一时间只有一个线程访问数据,在访问完成后再释放(解锁)互斥量。**在互斥量加锁之后,其他线程试图对该互斥量再次加锁时都会被阻塞,知道当前线程释放互斥锁。如果释放互斥量时有一个以上的互斥量,那么所有在该互斥量上阻塞的线程都会变成可运行状态,第一个变成运行的线程可以对互斥量加锁(哪个线程先运行哪个线程就可以对互斥量加锁),其他线程看到互斥量依然是锁着的,只能再次阻塞等待该互斥量。
互斥量用pthread_mutex_t数据类型表示,在使用互斥量之前,必须使用pthread_mutex_init函数对它进行初始化,注意,使用完毕后需调用pthread_mutex_destroy。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 两个函数返回值,成功返回0,否则返回错误码
pthread_mutex_init用于初始化互斥锁,mutexattr用于指定互斥锁的属性,若为NULL,则表示默认属性。除了用这个函数初始化互斥所外,还可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER。
pthread_mutex_destroy用于销毁互斥锁,以释放占用的内核资源,销毁一个已经加锁的互斥锁将导致不可预期的后果。
#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);
// 成功返回0,否则返回错误码
pthread_mutex_lock以原子操作给一个互斥锁加锁。如果目标互斥锁已经被加锁,则pthread_mutex_lock则被阻塞,直到该互斥锁占有者把它给解锁。
pthread_mutex_trylock和pthread_mutex_lock类似,不过它始终立即返回,而不论被操作的互斥锁是否加锁,是pthread_mutex_lock的非阻塞版本。当目标互斥锁未被加锁时,pthread_mutex_trylock进行加锁操作;否则将返回EBUSY错误码。注意:这里讨论的pthread_mutex_lock和pthread_mutex_trylock是针对普通锁而言的,对于其他类型的锁,这两个加锁函数会有不同的行为。
pthread_mutex_unlock以原子操作方式给一个互斥锁进行解锁操作。如果此时有其他线程正在等待这个互斥锁,则这些线程中的一个将获得它。
互斥量实例
/**
* 使用3个线程分别打印 A B C
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t g_mutex;
int g_cnt = 0;
void *func(void *arg)
{
int loop = 3;
int result = (int)arg;
printf("result=%d\n",result);
//while (loop > 0) {
//if (g_cnt % 3 == result) {
switch (result)
{
case 0: {
printf("--- a\n");
break;
}
case 1: {
printf("--- b\n");
break;
}
case 2: {
printf("--- c\n");
break;
}
default: {
return NULL;
}
}
pthread_mutex_lock(&g_mutex);
g_cnt++;
//loop--;
printf("g_cnt=%d\n",g_cnt);
pthread_mutex_unlock(&g_mutex);
// }
//}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t t1, t2, t3;
pthread_mutex_init(&g_mutex, NULL);
pthread_create(&t1, NULL, func, (void *)0);
pthread_create(&t2, NULL, func, (void *)1);
pthread_create(&t3, NULL, func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_mutex_destroy(&g_mutex);
return 0;
}
3.2.2 条件变量
条件变量是线程可用的一种同步机制,条件变量给多个线程提供了一个回合的场所,条件变量和互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。条件变量本身是由互斥体保护的,线程在改变条件状态之前必须首先锁住互斥量,其他线程在获取互斥量之前就不会觉察到这种变化,因为互斥量必须锁定之后才改变条件。
#include<pthread.h>
pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_destroy(pthread_cont_t *cond);
// 成功返回0,否则返回错误码
使用条件变量前调用pthread_cond_init初始化,使用完毕后调用pthread_cond_destroy做清理工作。除非需要创建一个具有非默认属性的条件变量,否则pthread_cond_init函数的attr参数可以设置为NULL。
#include<pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
// 成功返回0,否则返回错误码
传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait函数返回时,互斥量再次被锁住。
pthread_cond_broadcast用广播的形式唤醒所有等待条件变量的线程。pthread_cond_signal用于唤醒一个等待条件变量的线程,至于哪个线程被唤醒,取决于线程的优先级和调度机制。有时候需要唤醒一个指定的线程,但pthread没有对该需要提供解决方法。可以间接实现该需求:定义一个能够唯一表示目标线程的全局变量,在唤醒等待条件变量的线程前先设置该变量为目标线程,然后以广播形式唤醒所有等待条件变量的线程,这些线程被唤醒后都检查改变量是否是自己,如果是就开始执行后续代码,否则继续等待。
条件变量实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { fprintf(stderr, msg); exit(-1); } while(0)
pthread_cond_t cond;
void *r1(void *arg)
{
pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
static int cnt = 10;
while(cnt--)
{
printf("r1: I am wait.\n");
pthread_mutex_lock(mutex);
pthread_cond_wait(&cond, mutex); /* mutex参数用来保护条件变量的互斥锁,调用pthread_cond_wait前mutex必须加锁 */
pthread_mutex_unlock(mutex);
}
return "r1 over";
}
void *r2(void *arg)
{
pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
static int cnt = 10;
while(cnt--)
{
//pthread_mutex_lock(mutex); //这个地方不用加锁操作就行
printf("r2: I am send the cond signal.\n");
pthread_cond_signal(&cond);
//pthread_mutex_unlock(mutex);
sleep(1);
}
return "r2 over";
}
int main(void)
{
pthread_mutex_t mutex;
pthread_t t1, t2;
char* p1 = NULL;
char* p2 = NULL;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&t1, NULL, r1, &mutex);
pthread_create(&t2, NULL, r2, &mutex);
pthread_join(t1, (void **)&p1);
pthread_join(t2, (void **)&p2);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
printf("s1: %s\n", p1);
printf("s2: %s\n", p2);
return 0;
}