当我们在进行多线程编程的时候,如果有多个线程共享相同的内存时,我们需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取和修改的,或对每个线程是只读的,那么久不存在一致性的问题。但是,当一个线程可以修改变量,其他线程同样也能读取或修改变量的时候,我们就需要对这些线程进行同步。在Linux上进行多线程编程时,我们常用到互斥量(Mutex)。
互斥量(Mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放锁。当我们对互斥量进行加锁之后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有之前尝试对互斥量加锁的线程都会变成可运行状态,当第一个变为可运行的线程对互斥量加锁后,其他线程只能再次阻塞。在这种方式下,每次只有一个线程可以向前执行。
互斥变量时用pthread_mutex_t数据类型表示的。在使用互斥变量之前,必须对它进行初始化。我们可以通过调用pthread_mutex_init函数进行初始化,也可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只适用静态分配的互斥量)。
函数pthread_mutex_init和pthread_mutex_destroy的原型:
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *restrict mutex);
若成功,返回0;否则,返回错误编号
要用默认的属性初始化互斥量,只需把attr设为NULL。
对互斥量进行加锁,需要调用pthread_mutex_lock。如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。对互斥量解锁,需要调用pthread_mutex_unlock。
#include
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_trylock尝试对互斥量进行解锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么将会锁住互斥量,否则pthread_mutex_trylock就会失败,不能锁住互斥量,返回EBUSY。
上述,使用默认的属性其实已经能满足一些多线程同步的实现。但有时候我们可能需要设置互斥量的属性。下面,主要来谈谈互斥量的属性。
就像线程具有属性一样,线程的同步对象也有属性。值得注意的3个属性是:进程共享属性、健壮属性及类型属性。我们只讨论类型属性,至于进程共享属性和健壮属性有兴趣的朋友可以进行资料查找。
我们可以使用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy来反初始化。
#include
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
若成功,返回0;否则,返回错误编号
pthread_mutexattr_init函数将默认的互斥量属性初始化pthread_mutexattr_t结构。
PTHREAD_MUTEX_NORMAL 一种标准互斥量类型,不做任何特殊的错误检查或死锁检测。
PTHREAD_MUTEX_ERRORCHECK 此互斥量类型提供错误检查。
PTHREAD_MUTEX_RECURSIVE 此互斥量类型允许同一线程在互斥量解锁之前对该互斥量进行
多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数
不相同的情况下,不会释放锁。
PTHREAD_MUTEX_DEFAULT 此互斥量类型可以提供默认特性和行为。
我们可以使用pthread_mutexattr_gettype函数得到互斥量类型属性,用pthread_mutexattr_settype函数修改互斥量类型属性。
#include
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(const pthread_mutexattr_t *attr, int type);
若成功,返回0;否则,返回错误编号
下面,我们使用一个测试代码来看看在多线程中使用互斥量来同步线程的好处。
当然,我们的代码必须运行在多核系统中。
在未使用互斥量来同步时:
#include
#include
using namespace std;
#define THREAD_NUM 2
int g_iSum = 0;
//pthread_mutex_t mutex_t;
void *Func(void *pArg)
{
//pthread_mutex_lock(&mutex_t); //加锁
for (int i = 0; i < 1000; i++)
g_iSum++;
//pthread_mutex_unlock(&mutex_t); //解锁
pthread_exit((void *)1);
}
int main(void)
{
int iRet;
pthread_t ThreadFuncId[THREAD_NUM];
//pthread_mutex_init(&mutex_t, NULL);
for (int i = 0; i < THREAD_NUM; i++)
{
iRet = pthread_create(&ThreadFuncId[i], NULL, Func, NULL);
if (iRet != 0)
{
cout << "pthread_create Func return false!" << endl;
return -1;
}
}
for (int i = 0; i < THREAD_NUM; i++)
{
iRet = pthread_join(ThreadFuncId[i], NULL);
if (iRet != 0)
{
cout << "pthread_join Func return false!" << endl;
return -1;
}
}
//pthread_mutex_destroy(&mutex_t);
cout << "g_iSum = " << g_iSum << endl;
return 0;
}
在Linux多次上运行的结果不同,也不是我们想要的结果。
g_iSum = 2000
g_iSum = 2000
g_iSum = 1694
g_iSum = 1465
g_iSum = 2000
运行多次产生的结果。
在使用互斥量来同步时:
#include
#include
using namespace std;
#define THREAD_NUM 2
int g_iSum = 0;
pthread_mutex_t mutex_t;
void *Func(void *pArg)
{
pthread_mutex_lock(&mutex_t); //加锁
for (int i = 0; i < 1000; i++)
g_iSum++;
pthread_mutex_unlock(&mutex_t); //解锁
pthread_exit((void *)1);
}
int main(void)
{
int iRet;
pthread_t ThreadFuncId[THREAD_NUM];
pthread_mutex_init(&mutex_t, NULL);
for (int i = 0; i < THREAD_NUM; i++)
{
iRet = pthread_create(&ThreadFuncId[i], NULL, Func, NULL);
if (iRet != 0)
{
cout << "pthread_create Func return false!" << endl;
return -1;
}
}
for (int i = 0; i < THREAD_NUM; i++)
{
iRet = pthread_join(ThreadFuncId[i], NULL);
if (iRet != 0)
{
cout << "pthread_join Func return false!" << endl;
return -1;
}
}
pthread_mutex_destroy(&mutex_t);
cout << "g_iSum = " << g_iSum << endl;
return 0;
}
程序运行的结果都是正确的。