之前提到了QNX上的线程创建方法,现在进一步学习QNX上多线程的同步。曾经编写过多线程应用的同学们都知道线程之间的同步在多线程环境中特别重要,线程之间如果没有同步好,经常会出现逻辑错误。
有关QNX线程同步在QNX官方文档《QNX Neutrino Programmer’s Guide》和《Getting Started with QNX Neutrino》中都有详细的讲述,同学们可以从中学习相关的理论知识。不过,文档中的描述总是比较抽象,不如直接看样例来的简单。
在QNX Momentics环境中提供了使用Mutex同步线程的样例,同学们通过“File -> New -> Examples... ->QNX Neutrino Example -> QNX Example Neutrino Mutex Project”可以导入样例并测试。下面就根据这个样例分析一下QNX多线程同步中Mutex的使用。
线程之间之所以要同步,是因为不同线程可能会访问同一资源。如果两个线程各自使用自己的资源,就不会有线程同步的问题了。在QNX环境中,定义的全局变量是各线程共用的,线程方法内定义的变量是私有的。当不同线程要访问同一个全局变量时,就需要考虑线程同步的问题了。
我们从QNX Momentics的Mutex样例中可以看到两个变量的定义:firstname和lastname。两个变量都定义在具体方法外,属于全局变量。
#include <stdlib.h>
#include <stdio.h>
//…
char *firstname;
char *lastname;
int main(int argc, char *argv[]) {
//…
然后,方法bushchanger_nomutex和kerrychanger_nomutex都会访问这两个变量,这两个方法的定义如下,其中的delay方法是为了让效果更加明显:
void bushchanger_nomutex() {
while (1) {
strcpy(firstname, "George");
delay(55);
strcpy(lastname, "Bush");
delay(55);
}
}
void kerrychanger_nomutex() {
while (1) {
strcpy(firstname, "John");
delay(77);
strcpy(lastname, "Kerry");
delay(77);
}
}
这时候考虑一下,如果启动两个线程,一个调用bushchanger_nomutex,另一个调用kerrychanger_nomutex,会出现什么情况呢?
如果调用bushchanger_nomutex的线程1在设置了firstname等于“George”后恰好轮到调用kerrychanger_nomutex线程2运行,而线程2恰好又从strcpy(lastname, "Kerry");开始运行,则这个人的名字变成了George Kerry,这就导致了运行错误。本来设计人员是希望设置成George Bush或者是John Kerry的。
这种情况下就需要使用线程同步机制。QNX中的线程同步机制有多种,最简单的就是Mutex。Mutex的基本概念就是一个锁,使用一个共享资源是先将锁锁上,然后对资源进行处理,处理完了再将锁打开。如果在资源处理过程中有其他线程需要处理同一资源,就让那个线程等待这个锁,当第一个线程处理完了,开了锁,第二个线程才对资源进行处理。同样的原因,第二个线程在处理资源之前也需要先将锁锁上,处理完了再开锁。
为了使用Mutex,需要先定义一个全局变量,类型是pthread_mutex_t,pthread_mutex_t是pthread.h中定义好的类型。样例代码如下:
pthread_mutex_t presidentmutex;
然后,当一个线程需要处理共享资源的时候就调用pthread_mutex_lock()方法锁上Metux,该方法需要一个pthread_mutex_t类型的参数,就使用刚才定义的presidentmutex。调用代码如下:
pthread_mutex_lock(&presidentmutex);
当资源处理完后,就调用pthread_mutex_unlock()方法将Mutex解锁,该方法同样需要一个pthread_mutex_t类型的参数,还是使用刚才定义的presidentmutex。调用代码如下:
pthread_mutex_unlock(&presidentmutex);
最后整体看看样例中修改过的使用Mutex机制进行同步的方法:
void bushchanger_mutex() {
while (1) {
// lock the mutex, ensure we have exclusive access.
pthread_mutex_lock(&presidentmutex);
strcpy(firstname, "George");
delay(55); // delay to make the problem more visible
strcpy(lastname, "Bush");
pthread_mutex_unlock(&presidentmutex);
delay(55);
}
}
void kerrychanger_mutex() {
while (1) {
pthread_mutex_lock(&presidentmutex);
strcpy(firstname, "John");
delay(77); // delay to make the problem more visible
strcpy(lastname, "Kerry");
pthread_mutex_unlock(&presidentmutex);
delay(77);
}
通过以上方法,两个线程使用firstname和lastname变量时就不会有冲突了。当线程1开始使用firstname和lastname变量是先将presidentmutex锁锁上。这种情况下,即时线程1在写完firstname后暂停运行,线程2也不会对firstname或者是lastname进行修改,因为线程2会一直在pthread_mutex_lock那一句等着,直到线程1调用了pthread_mutex_unlock方法。
这就是QNX上Mutex的使用方法。
最后,作为程序员的你,抬头看看世界,知道George Bush是谁吗?呵呵,你应该听说过吧,那John Kerry又是谁?在搜索引擎盛行的时代,相信你很快会找到答案。问题是,没有人问你的时候,你看到这段样例是会有这样的好奇心吗?好奇心可是学习的第一动力呀!