简体   繁体   中英

Singleton multithread code in C++

I have a doubt related to Singleton and multithread programming in C++ Following you can see an example code of a Singleton class with a variable named shared.

I create 1000 threads that modify (+1) that variable of my Singleton global instance. The final value of shared is 1000 but I would expect this value to be under 1000 since I am not protecting this variable for concurrency.

Is the code really thread safe because the class is Singleton or it just happened to be lucky and the value is 1000 but it can perfectly be less than 1000?

#include <iostream>
using namespace std;

class Singleton {
private:
    Singleton() {shared = 0;};
    static Singleton * _instance;
    int shared;

public:
    static Singleton* Instance();
    void increaseShared () { shared++; };
    int getSharedValue () { return shared; };
};

// Global static pointer used to ensure a single instance of the class.
Singleton* Singleton::_instance = NULL;

Singleton * Singleton::Instance() {
    if (!_instance) { 
        _instance = new Singleton;
    }

    return _instance;
}

void * myThreadCode (void * param) {
    Singleton * theInstance;
    theInstance = Singleton::Instance();
    theInstance->increaseShared();

    return NULL;
}

int main(int argc, const char * argv[]) {
    pthread_t threads[1000];
    Singleton * theInstance = Singleton::Instance();

    for (int i=0; i<1000; i++) {
        pthread_create(&threads[i], NULL, &myThreadCode, NULL);
    }

    cout << "The shared value is: " << theInstance->getSharedValue() << endl;

    return 0;
}

Is the code really thread safe because the class is Singleton or it just happened to be lucky and the value is 1000 but it can perfectly be less than 1000?

You got lucky...

In reality, the most likely issue with what you're observing has to-do with the fact that the time it takes to increment the value of your singleton on your specific machine is less than the time it takes the operating system to allocate the resources to launch an individual pthread. Thus you never ended up with a scenario where two threads contend for the unprotected resources of the singleton.

A much better test would have been to launch all of your pthreads first, have them block on a barrier or condition variable, and then perform the increment on the singleton once the barrier's condition of all the threads being "active" is met ... at that point you would have been much more likely to have seen the sorts of data-races that occur with non-atomic operations like an increment operation.

If you implement your Singleton like this, the singleton creation will be thread safe:

Singleton & Singleton::Instance() {
    static Singleton instance;
    return instance;
}

Since the instance can never be null, and no memory to manager, a reference is returned instead of a pointer.

The increment operation can be made atomic by using platform specific operations (g++ provides built-ins, eg __sync_fetch_and_add ), or C++11 atomic from STL, or Boost.Atomic, or with mutex guards.

std::atomic<int> shared;

void increaseShared () { ++shared; };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM