先贴示范代码:
//--------------------tmutex.h开始------------------------------
//实现linux的互斥量c++封装
#ifndef TMUTEX_H
#define TMUTEX_H
#include <pthread.h>
//线程互斥量
struct ThreadMutex
{
ThreadMutex()
{
pthread_mutex_init(&mtx,NULL);
}
~ThreadMutex()
{
pthread_mutex_destroy( &mtx );
}
inline void lock()
{
pthread_mutex_lock( &mtx );
}
inline void unlock()
{
pthread_mutex_unlock( &mtx );
}
pthread_mutex_t mtx;
};
//空互斥量,即调用lock时什么事都不做。
struct NullMutex
{
inline void lock()
{
}
inline void unlock()
{
}
};
template<class T>
class CAutoGuard
{
public:
CAutoGuard(T &mtx) : m_mtx(mtx)
{
m_mtx.lock();
}
~CAutoGuard()
{
m_mtx.unlock();
}
protected:
T &m_mtx;
};
#define AUTO_GUARD( guard_tmp_var, MUTEX_TYPE, mtx )
CAutoGuard<MUTEX_TYPE> guard_tmp_var(mtx)
#endif
//-------------------------tmutex.h结束------------------------------------------
//-------------------------主程序文件test.cpp开始----------------------------------
#include <pthread.h>
#include "tmutex.h"
#include <iostream>
using namespace std;
typedef ThreadMutex MUTEX_TYPE; //使用线程互斥量的互斥量类型
//typedef NullMutex MUTEX_TYPE; //不使用互斥量的互斥量类型
MUTEX_TYPE g_mtx; //互斥量变量定义
void *print_msg_thread(void *parg);
void *print_msg_thread(void *parg)
{//工作线程,用循环模拟一个的工作。
char *msg = (char *)parg;
AUTO_GUARD( gd, MUTEX_TYPE, g_mtx );
for(int i=0; i<10; i++ )
{
cout << msg << endl;
sleep( 1 );
}
return NULL;
}
int main()
{
pthread_t t1,t2;
//创建两个工作线程,第1个线程打印10个1,第2个线程打印10个2。
pthread_create( &t1, NULL, &print_msg_thread, (void *)"1" );
pthread_create( &t2, NULL, &print_msg_thread, (void *)"2" );
//等待线程结束
pthread_join( t1,NULL);
pthread_join( t2,NULL);
return 0;
}
//-----------------------------主程序文件test.cpp结束
看了上面的示例代码及注释,相信已经了解该代码的功能。我们在主程序中创建两个线程,第1个线程循环打印10个1,第2个线程循环打印10个2。由于线程的特性,两个线程并不一定会按顺序执行,它们可能会被轮流调度执行。
如果两个线程被轮流调度执行,那么所打印的10个1和10个2的排列顺序则不固定。线程1打印了几个字符后,可能会别打断,CPU被分配到线程2上去执行。这样可以尽可能让每个线程都得到CPU资源。但是另一方面也带来了问题。如果两个线程共同访问了一个变量。并且两个线程都会修改它,在修改未完成被打断的话,会使得最后修改的结果和预期的不一致。对于不能被打断的操作我们叫它原子操作。为了能使线程中的某段代码成为原子操作,我们就得使用互斥量。如本例所示的打印10个字符,如果我们不使用互斥量那么这个打印顺序就会被破坏,使用了互斥量后,线程1未离开互斥量所管的区域,线程2是不能再次进入的。这就保证了打印过程的原子操作性。
Linux中使用临界区加锁的方法是用pthread_mutex_t进行操作,分别调用pthread_mutex_init、 pthread_mutex_destroy创建和释放pthread_mutex变量,调用pthread_mutex_lock和 pthread_mutex_unlock进行加锁和解锁。其中pthread_mutex_init和pthread_mutex_destroy只要在最开始的时候和不用的时候各调用一次,pthread_mutex_lock和pthread_mutex_unlock则是在每次加锁和解锁时调用。要注意的是它们的调用必须一一对应。
本例的互斥量使用了C++的构造和析构以及模板的特性进行封装,保证分配和释放、加锁和解锁的成对,使得互斥量的使用更加简单。加锁时只需一个语句:AUTO_GUARD( gd, MUTEX_TYPE, g_mtx ); 该语句是个宏,展开宏得到的代码是:CAutoGuard<MUTEX_TYPE> gd(g_mtx); CAutoGuard对象的构造和析构自动调用g_mtx的lock和unlock函数进行加锁解锁。而锁的类型就看MUTEX_TYPE的定义了。下面这两行是互斥量锁类型的定义:
typedef ThreadMutex MUTEX_TYPE; //使用线程互斥量的互斥量类型
//typedef NullMutex MUTEX_TYPE; //不使用互斥量的互斥量类型
其中第1行的类型是ThreadMutex,我们看该struct的定义,在lock和unlock函数中分别调用了pthread_mutex_lock和pthread_mutex_unlock,这样就实现了资源的锁定和解锁。
而第2行的类型是NullMutex,在该struct的定义中,lock和unlock函数都是空函数,没有执行任何锁定解锁操作。
因此,将MUTEX_TYPE的类型改为ThreadMutex或NullMutex就可以实现使用或不使用互斥量的效果。
将上述两个文件保存并编译:g++ tmutex.h test.cpp -lpthread -o test
编译完输出test可执行文件。输入./test执行程序。下面是使用互斥量和不使用互斥量的执行结果:
使用互斥量:
[root@hjclinux sampthread]# g++ tmutex.h test.cpp -lpthread -o test
[root@hjclinux sampthread]# ./test
1
1
1
1
1
1
1
1
1
1
2
2
2
2
2
2
2
2
2
2
将test.cpp中的MUTEX_TYPE定义改成typedef NullMutex MUTEX_TYPE再编译执行结果如下:
[root@hjclinux sampthread]# ./test
1
2
2
1
2
1
2
1
2
1
2
1
2
1
2
1
2
1
2
1
由于线程调度的关系,可能每次执行打印出1和2的顺序都不一样。