笔试题

class Runnable {
protected:
    Runnable () : thread(NULL) {
	pthread_create(&thread, NULL, Runnable::Run, this);
    }
    ~Runnable () {
        if (NULL != thread) {
            // Destroy thread.
            pthread_join(thread, NULL);
        }
    }   
protected:
    // Override this interface which extended runnable.
    virtual void Run (void) {
    }
private:
    static void* Run (void* args) {
        Runnable* runnable = static_cast<Runnable*>(args);
        runnable->Run();
        return NULL;
    }     

private:
    pthread_t     thread;
};
class Worker : public Runnable {
public:
    Worker () : quit(false) {
    }
    ~Worker () {
        // Set quit flag.
        quit = true;
    }

protected:
    // Override thread run function.
    void Run (void) override { 
        while (!quit) {
        // TODO somethine
        }
    }

private:
    volatile bool quit;                 // Worker thread quit flag.
    // OTHER member variables
};

       请找出上面程序的Bug。

案例分析

       上面笔试题的代码在一定概率下会崩溃或无响应,原因这与C++成员变量和构造函数的初始化顺序有关。在C++中任何变量或者对象的创建都可以看做如下两个步骤:

  1. 申请与类型一样大小的内存,基本等同于malloc(sizeof(T));
  2. 调用该类型的构造函数实现对象的初始化操作,因为申请的内存的值是随机值;

这本身是两个步骤的事情让C++编译器合二为一,程序员在大意的情况下下很可能简化为一步,这就会让BUG有可乘之机。如果是通过层层的继承后类的构造函数,C++会优先调用父类的构造函数,再调用子类的构造函数,以此类推。

说道这里不知道读者们有没有意识到上面代码的Bug所在?问题就出在下面这块代码:

Worker () : quit(false) {
}

Worker是Runnable的子类,也就是说程序会优先执行Runnable的构造函数:

Runnable () : thread(NULL) {
    pthread_create(&thread, NULL, Runnable::Run, this);
}

从上面的代码可以看看出Runnable的构造函数创建了一个线程,这个线程的函数因为Worker类的重写而执行Worker::Run()。线程创建完成后程序开始执行Worker的构造函数,即将quit设置成false。问题就出在这里,线程优先于quit的初始化创建,在一定概率上线程函数在执行了quit还没有完成初始化,结果就是线程函数直接退出了,因为线程函数在判断quit值的时候还是一个随机值。程序认为线程创建了,但是线程函数已经退出了,外在表现就是僵死了。

       除了上面说的Bug以外,在Worker析构的时候也存在问题,因为C++析构的顺序是优先执行子类的析构函数,再执行父类的析构函数,以此类推。这造成的问题是Worker对象已经执行析构函数了,但是线程函数可能还在执行,如果此时线程函数操作了Worker对象的成员变量就会有崩溃的风险。

       一个看似很简单的代码,却因为一些小细节造成难以发觉的BUG,尤其是在CPU核数较少的工位机。因为在核数较多的服务器上,创建的线程可以立即执行,而核数较少时,创建的线程因为没有时间片大概率会落后于父线程,致使BUG难以发觉。

附加题

类内有多个成员变量,而成员变量的初始化顺序是定义成员变量时决定的,而不是构造函数里赋值的顺序决定的,记得网上有类似的笔试题。

class Type {
public:
    Type() :b(2), a(1){
    }

private:
    int a;
    int b;
};

上面代码实际的执行顺序是a赋值为1,b赋值为2。