线程同步
首先,我们要明确互斥量和条件变量是线程用来同步彼此行为的两个工具。
众所周知,线程的主要优势在于可以通过全局变量来共享信息,但是在共享信息的时候必须满足以下两个条件:
- 必须确保多个线程不会同时修改同一个变量
- 某一个线程不会读取正由其他线程修改的变量。
保护对共享变量的访问:互斥量
术语临界区是指::访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是,同时访问同一共享资源的其他线程不应该中断该片段的执行。
可以使用互斥量来保证对任意共享资源的原子访问,是保护共享变量最常见的方法
互斥量的两种状态:
- 已锁定
- 未锁定
注意:任何时候最多只有一个线程可以锁定该互斥量。一旦某一线程锁定该互斥量,将成为该互斥量的所有者,只有所有者才能给互斥量解锁。
一般情况下,对每一共享资源会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议: - 针对共享资源锁定互斥量
- 访问共享资源
- 对互斥量解锁
互斥量的静态分配和动态分配
互斥量的静态分配
互斥量属于pthread_mutex_t类型的变量,在使用之前必须对其初始化。
pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER
动态初始化互斥量
静态初始值PTHREAD_MUTEX_INITIALIZER只能对经由静态分配且携带默认属性的互斥量进行初始化,其他情况下,必须调用pthread_mutex_init()对互斥量进行初始化。
#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
返回零成功。
参数mutex指定函数执行初始化操作的目标互斥量,参数attr是指向pthread_mutexattr_t类型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量属性,设为NULL,则取默认属性。
在什么情况下必须使用函数pthread_mutex_init()函数来动态分配互斥量,而非静态初始化互斥量呢?
- 动态分配于堆中的互斥量。
- 互斥量是在栈中分配的自动变量。
- 初始化经由静态分配,且不使用默认属性的互斥量。
互斥量的销毁
当不再需要经由自动或动态分配的互斥量时,应使用下面的函数将其销毁(使用默认属性静态初始化的互斥量不需要调用之)。
#include<pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);
返回0成功。
销毁安全的前提是:
1,互斥量处于未锁定的状态
2,后续无任何线程企图锁定它
经由其销毁的互斥量,可调用pthread_mutex_init()对其重新初始化。
加锁和解锁互斥量
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
//both return 0 on success.
在下面我将贴出两个代码来说明互斥量的作用,为什么说互斥量可以保证对任意共享资源的原子访问,原子访问归根接地有是什么意思?
首先,我们来看这一段代码,两个线程同时执行一个临界区:
#include <stdio.h>
#include <pthread.h>
static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *threadfunc(void *arg)
{
int loops = *((int *) arg);
int j;
for (j = 0; j < loops; j++){
glob++;
}
return NULL;
}
int main()
{
pthread_t t1,t2;
int loops = 10000;
pthread_create(&t1, NULL, threadfunc, &loops);
pthread_create(&t2, NULL ,threadfunc, &loops);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("glob = %d\n", glob);
return 0;
}
我们预期的值应该是2000,但事实上并不是,它是一个大于10000小于20000的随机值。
那么如果我对临界区加锁以后呢?
#include <stdio.h>
#include <pthread.h>
static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *threadfunc(void *arg)
{
int loops = *((int *) arg);
int j;
pthread_mutex_lock(&mtx);
for (j = 0; j < loops; j++){
glob++;
}
pthread_mutex_unlock(&mtx);
return NULL;
}
int main()
{
pthread_t t1,t2;
int loops = 10000;
pthread_create(&t1, NULL, threadfunc, &loops);
pthread_create(&t2, NULL ,threadfunc, &loops);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("glob = %d\n", glob);
return 0;
}
这个时候我们保证了对临界区的原子访问,这样我们就总是能够得到我们的预期值了。
互斥量类型
在这了浅谈几个互斥量的类型,需要的时候请我自己在自行查阅资料吧。
PTHREAD_MUTEX_NORMAL:
该类型的互斥量不具有死锁检测功能(自检)。
PTHREAD_MUTEX_ERRORCHECK:
对此类互斥量的所有操作都会执行错误检查。
PTHREAD_MUTEX_RECURSIVE:
递归互斥量维护有一个锁计数器。