简体   繁体   中英

Random function call from multiple threads in Qt/C++

I have a multi-thread QT application that sometimes need a random alphanumeric string from one of its threads (some threads start at application startup, others start or die during lifetime), and I would like to obtain that by calling a function defined in a common header, to avoid code replication.

Here there's a code snippet:

QString generateRandomAlphanumericString(int length)
{
    qsrand(static_cast<uint>(QTime::currentTime().msec())); //bad
    QString randomAS = QString();

    static const char alphanum[] =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";

    for (int i = 0; i < length; ++i)
        randomAS[i] = alphanum[qrand() % (sizeof(alphanum) - 1)];

    return randomAS;
}

I initially did some mistakes.

At the beginning I called qsrand(static_cast<uint>(QTime::currentTime().msec())); in the main function, but I've learned that it should be done per-thread .

Then I put the qsrand call in the function above, but it's not correct .

Please consider that at program startup many threads start "together", so if I initialize the seed with current time in msec the seed is the same among them.

Is there a way to change that function accordingly without modify all points in my application where a thread starts its life? Any implementation done in pure C++ (without the use of QT) is fine. Could the new random C++11 library help in some way to achieve my task?

void InitSeedForThread(uint globalSeed, int myThreadIndex)
{
    qsrand(globalSeed);
    for (int i = 0; i < myThreadIndex; ++i)
        qrand();
}

auto GetRandom(int numThreads)
{
    for (int i = 0; i < numThreads - 1)
        qrand();
    return qrand();
}

Given an ordered list of numbers A, B, C, D, E, F, G, H, ... splits it into n lists. If n was 4, you would get

1. A, E, I, ...
2. B, F, J, ...
3. C, G, K, ...
4. D, H, L, ...

Con: Doing RNG is somewhat expensive, and you're repeating a lot of work. However, since you're doing QT (UI-bound) I'm assuming that performance isn't an issue.

Alternatively, you could do a global random function with a mutex, but that ain't free either.

I finally found a good solution (thanks everybody who has contributed with comments):

enum ThreadData {TD_SEED};
static QThreadStorage<QHash<ThreadData, uint> *> cache;

inline void insertIntoCache(ThreadData data, uint value)
{
    if (!cache.hasLocalData())
        cache.setLocalData(new QHash<ThreadData, uint>);
    cache.localData()->insert(data, value);
}

inline void removeFromCache(ThreadData data)
{
    if (cache.hasLocalData())
        cache.localData()->remove(data);
}

inline bool hasInCache(ThreadData data)
{
    if (!cache.hasLocalData()) return false;
    return cache.localData()->contains(data);
}

inline uint getCachedData(ThreadData data)
{
    if (cache.hasLocalData() && cache.localData()->contains(data))
        return cache.localData()->value(data);
    return 0;
}

inline int getThRandom()
{
    uint seed = 0;
    if (!hasInCache(TD_SEED))
    {
        seed = QDateTime::currentMSecsSinceEpoch() % 100000000;
#ifdef Q_OS_WIN     
        seed += GetCurrentThreadId();
#else
        seed += QThread::currentThreadId();
#endif
        qsrand(static_cast<uint>(seed));
        insertIntoCache(TD_SEED, seed);
    }
    else {
        seed = getCachedData(TD_SEED);      
    }

    return qrand();
}

Basically, as suggested by Igor I've made use of QThreadStorage to store a seed for each thread. I've used an hash for future extensions. Then, I've made use of QDateTime::currentMSecsSinceEpoch() instead of QTime::currentTime().msec() to have a different number across multiple application starts (if for example the random generated value is stored in a file/db and should be different). Then, I've add an offset, as suggested by UKMonkey , using the thread ID.

So, my original function will be:

QString generateRandomAlphanumericString(int length)
{
    QString randomAS = QString();

    static const char alphanum[] =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";

    for (int i = 0; i < length; ++i)
        randomAS[i] = alphanum[getThRandom() % (sizeof(alphanum) - 1)];

    return randomAS;
}

I've run some tests, producing from different threads thousand of alphanumeric strings, storing them to multiple files and double checked for duplicates among them and between multiple application run.

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