看了上面的示例代码及注释,相信已经了解该代码的功能。我们在主程序中创建两个线程,第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的顺序都不一样。