1

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?

scopchanov
  • 7,966
  • 10
  • 40
  • 68
ABCplus
  • 3,981
  • 3
  • 27
  • 43
  • 1
    I would like to know why you think `qsrand` should be called on each thread? (since i'm not familiar to QT) – apple apple Jan 25 '18 at 14:55
  • I suppose you could use `QThreadStorage` to have a per-thread flag indicating whether `qsrand` has been called on this thread yet. If in fact `qsrand` needs to be called on every thread. – Igor Tandetnik Jan 25 '18 at 14:57
  • `thread_local` ? – Dmitry Sazonov Jan 25 '18 at 15:00
  • `so if I initialize the seed with current time in msec the seed is the same among them.` you don't have to give it the time; you can always give it a time + offset and adjust the offset for each thread... but I don't think this is really related to your issue – UKMonkey Jan 25 '18 at 15:00
  • 1
    `Could the new random C++11 library help in some way to achieve my task?` yes; but you'd have to research and try it. – UKMonkey Jan 25 '18 at 15:01
  • @appleapple: I've edit my question with a link. It's not related to QT – ABCplus Jan 25 '18 at 15:07
  • @ABCplus your update contains link about `std::srand`, not `qsrand` as you mentioned. – apple apple Jan 25 '18 at 15:09
  • if you really need/want to seed it, here is some hint: you can always seed a random with values other than current time. – apple apple Jan 25 '18 at 15:13
  • @appleapple `qsrand` calls `srand` apart of Android environment. – ABCplus Jan 25 '18 at 15:16
  • @IgorTandetnik: could help, but 2 threads started one after the other could have the same seed if it's generated using `QTime::currentTime().msec()`. Maybe I should add an offset as @UKMonkey suggested....thread ID?! – ABCplus Jan 25 '18 at 15:20
  • 1
    @ABCplus as I mentioned in my previous comment, you can use any value as seed. And there is a more serious problem, you should not seed RNG every time you call `generateRandomAlphanumericString`, especially when you seed them with current time. – apple apple Jan 25 '18 at 15:24
  • @appleapple: I understand, but 2 threads with the same seed generate the same random numbers. So, time in msec cannot be used since if they threads start "together" the seed is the same. I fully agree with seed generated every time in my function, in fact is marked as "bad" in inline comment – ABCplus Jan 25 '18 at 15:35
  • 1. seed RNG with something other than time. 2. seed them at start of thread. (if needed). – apple apple Jan 25 '18 at 15:39

2 Answers2

0
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.

Humphrey Winnebago
  • 1,512
  • 8
  • 15
0

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.

ABCplus
  • 3,981
  • 3
  • 27
  • 43