-1

Main point up front: What do I need to do change to display the correct values for heads/tails in my records?

Edit 1: The arrays of integers inside the Record appear to fill with random values once there is more than 1 thread.

I've been trying to debug this for a few days now. My code is complete and fully executes. (Just so everyone knows, I'm a student and not pretending to be a professional programmer.)

In my multi-threaded coin toss program, I'm trying to capture the amount of times heads or tails occurs in each thread. I'm flipping a coin a total of 100,000,000 times. (One-hundred million.)

The elements in the arrays inside my record class are not accumulating correctly. I know I don't need to use mutex locks, because each thread is accessing a separate memory location unique to the thread. But the threads are updating the values incorrectly, which shouldn't be happening.

Here are examples from my debug.

  1. Single thread (notice the correct amount of heads/tails):

Debug output of single thread.

  1. Two threads (now heads/tails count just goes crazy.):

Debug output of two threads.

Fully executable code sample is below, followed by sample output. Also, here is link to full code on Github as well:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <random>
#include <algorithm>
#include <time.h>
#include <strstream>
#include <random>


using namespace std;

default_random_engine dre;
uniform_int_distribution<int> Tosser(0,1);

struct Record {
    Record();
    ~Record();
    int numThreads;
    int *heads;
    int *tails;
    time_t startTime;
    time_t stopTime;
    time_t duration;
    int timesToToss;
};

Record::Record(): numThreads(0), heads(NULL), tails(NULL), startTime(NULL), stopTime(NULL), timesToToss(0){}
Record::~Record() {
    startTime = NULL;
    stopTime = NULL;
    duration = NULL;
    timesToToss = NULL;
    delete [] heads;
    heads = NULL;
    delete [] tails;
    tails = NULL;
    numThreads = NULL;
}

void concurrency(){
    vector<thread> threads;

    Record *records = new Record[4];
    Record *recPtr;
    int *numThrPtr;
    int *headsPtr;
    int *tailsPtr;
    time_t *startTimePtr;
    time_t *stopTimePtr;

    vector<time_t> durations;

    int timesToToss = 100000000; // Times to flip the coin.
    int index = 0; // Controls which record is being accessed.

    for(int i=1;i<3;i*=2){ //Performs 2 loops. 'i' is calculated to represent the number of threads for each test: 1, and 2 (full code contains up to 8 threads.)

        recPtr = &records[index]; //Get the address of the next record in the Record array.
        recPtr->timesToToss = timesToToss; //
        recPtr->numThreads = i; //Record the quantity of threads.
        recPtr->heads = new int[recPtr->numThreads]; //Create a new heads array, of 'x' elements, determined by number of threads.
        recPtr->tails = new int[recPtr->numThreads]; //Create a new tails array, of 'x' elements, determined by number of threads.
        recPtr->startTime = time(0); //Record the start time.

        for(int j = 0;j<recPtr->numThreads;j++){ //Start multi-threading.

            headsPtr = &recPtr->heads[j]; // Get the address of the index of the array, one element for each thread for heads.
            tailsPtr = &recPtr->tails[j]; // Get the address of the index of the array, one element for each thread for heads.

            threads.push_back(thread([&headsPtr, &tailsPtr, timesToToss](){for(int k=0;k<timesToToss;k++){ if (Tosser(dre)) ++(*headsPtr); else ++(*tailsPtr); } })); //Toss a coin!
        }

        for(auto& thread: threads) thread.join(); // Collect/join all the threads.

        while(!threads.empty()){ //Don't want to try and join 'live' threads with 'dead' ones!
            threads.pop_back();//Clear out the threads array to start with an empty array the next iteration.
        }

        recPtr->stopTime = time(0); //Record the end time.

        recPtr->duration = recPtr->stopTime - recPtr->startTime;

        timesToToss /= 2; //Recalculate timesToToss.
        ++index; //Increase the index.
    }

    for (int i=0;i<4;i++){ //Display the records.
        recPtr = &records[i];
        cout << "\nRecord #" << i+1 << ", " << recPtr->numThreads << " threads.";
        cout << "\nStart time: " << recPtr->startTime;
        cout << "\nStop time: " << recPtr->stopTime;
        cout << "\nTossed " << recPtr->timesToToss << " times (each thread).";
        cout << "\nHeads appeared << " << recPtr->heads << " times.";
        cout << "\nTails appeared << " << recPtr->tails << " times.";
        cout << "\nIt took " << recPtr->duration << " seconds.";
        durations.push_back(recPtr->duration);
        cout << "\n" << endl;
    }

    sort(durations.begin(),durations.end());

    cout << "Shortest duration: " << durations[0] << " seconds." << endl;

    delete [] records;
    records = NULL;
}

int main() {

    concurrency();

    return 0;
}

Output for recored #2 is[updated 5/5/2016 @ 2:16pm CST]:

Record #2, 2 threads.
Start time: 1462472702
Stop time: 1462472709
Tossed 50000000 times (each thread).
Heads appeared << 474443746 times.
Tails appeared << -1829315114 times.
It took 7 seconds.

Shortest duration: 3 seconds.

Process finished with exit code 0
NonCreature0714
  • 5,744
  • 10
  • 30
  • 52
  • 1
    `int *heads;` is a pointer, so `<<` will print its address. If you intend for this to be a count, why is this a pointer? – user4581301 May 05 '16 at 18:16
  • 1
    OK. I see this now. Each thread has its own counter. You will need to sum each thread's counters and print the sum. – user4581301 May 05 '16 at 18:17
  • Because I need the random access that arrays provide. I did just realize that I'm using the head of the pointer. – NonCreature0714 May 05 '16 at 18:18
  • Yes, correct! :) Thanks – NonCreature0714 May 05 '16 at 18:19
  • This post is huge. I have no idea what's going on. Can you clean it up? – Lightness Races in Orbit May 05 '16 at 18:44
  • @LightnessRacesinOrbit What do you want cleaned? I can remove the narrative, debug, code, or output. If I remove the brief narrative, the explanation/clarity is lost. If I remove debug, a clear example of the root problem is removed. If I remove my code, then people will have to go to Github. If I remove my output, I provide no example of my output. Give me something specific, and I'll do it. – NonCreature0714 May 05 '16 at 18:48
  • 1
    Deleted my answer because sorting this out all the way will take more time than I can afford to spend at the moment. – user4581301 May 05 '16 at 18:55
  • @user4581301 Thanks for the help, at least you resolved one issue. – NonCreature0714 May 05 '16 at 18:56
  • Well can you just pose a single, simple question with an accompanying [MCVE]? _"My entire code is below"_ is a big red flag. We're not a consultancy, and I can't see how this post is ever going to assist anybody else in its current form. – Lightness Races in Orbit May 05 '16 at 18:58
  • _"Edit 2: 'This question does not show any research effort.' Seriously? Your down vote shows no reading effort."_ And that attitude is not helping your case... – Lightness Races in Orbit May 05 '16 at 19:00
  • 1
    @LightnessRacesinOrbit My code is specifically to test for 1, 2, 4, and 8 threads. Which takes more than a few statements to complete. I'm sure if people can't copy/paste, and run, it would be incomplete. It's as minimal as it gets. I have only one question: "What do I need to do change to display the correct values for heads/tails in my records?" My example is complete, and verifiable. You're also not helpful. – NonCreature0714 May 05 '16 at 19:01
  • _"It's as minimal as it gets"_ No, it's not. I'm done arguing with you. Presenting a [MCVE] is _clearly_ stated as one of the requirements for questions of this kind. It's not my fault you are ignoring those requirements. Good luck. – Lightness Races in Orbit May 05 '16 at 19:02
  • 1
    @LightnessRacesinOrbit minimal != small. It means the least required. I have the least required to make it complete and verifiable, therefore minimal. Sorry it takes you so long to read. Goodbye. – NonCreature0714 May 05 '16 at 19:04
  • 1
    The other major issue is after `recPtr->heads = new int[recPtr->numThreads];` you don't initialize that new storage. That's Undefined Behaviour dude. I checked out a hack fix for that, but there is still a logic problem. – user4581301 May 05 '16 at 19:04
  • 1
    Non, you can remove all of the code for everything but the two thread case. This demonstrates the error without all of the other fluff. Minimal doesn't mean small, true, but it does mean that it does as little as possible to duplicate the problem. – user4581301 May 05 '16 at 19:06
  • @user4581301 Thanks for being constructive, I'll work on it. – NonCreature0714 May 05 '16 at 19:08
  • Even though you don't need a mutex, you *do* need atomics. – o11c May 05 '16 at 19:17
  • 1
    Atomics would make his life easier, but nothing here shares a variable. Non's made sure of that. Except... By the time the thread starts, `headPtr` and `tailsPtr` have been repointed. and mother fud. That's what I missed eariler. In `[&headsPtr, &tailsPtr, timesToToss]` capturing pointers to `headPtr` and `tailsPtr` kills you dead. Everyone is writing to the same head and tails pointer because the pointer changed got changed out from under the lambda. – user4581301 May 05 '16 at 19:25
  • @user4581301 Wow! That's brilliant! How can I pass the address to lambda and not repoint inside the thread? Could I declare any another pointer in lambda that isn't affected by repoint? I'm asking because I don't even know if it's possible. – NonCreature0714 May 05 '16 at 19:27
  • Resurrected the answer with the third fix. – user4581301 May 05 '16 at 19:33
  • I think a lot of your problems might go away if you used `std::vector` instead of all these manually dynamically allocated arrays. It would also simplify your code as well, making it smaller and easier to reason about. – GManNickG May 05 '16 at 19:35
  • 1
    Once all the logic is worked out and tested, you can ditch computing and tracking tails. Tails is either `timesToToss - heads` or you have a bug. – user4581301 May 05 '16 at 19:43
  • @GManNickG That is a good suggestion, and were I started, but the vector of threads didn't agree with the vector of records when I was trying to randomly access locations in the vector from vector. – NonCreature0714 May 05 '16 at 19:44

1 Answers1

5

int *heads; defines heads to be a pointer.

The default behaviour of << is to print the address of a pointer, not the data pointed at. There is an exception for pointers to char to print out c-style strings.

Since each thread gets its own int to count the number of heads generated by the thread, when the threads have completed the head counts must be summed and the sum printed.

In addition,

recPtr->heads = new int[recPtr->numThreads]; 

allocated storage for head counters, but nothing I can find in the code initializes them. This be Undefined behaviour. A simple hack fix for this is:

    for(int j = 0;j<recPtr->numThreads;j++){

        recPtr->heads[j] = 0;
        recPtr->tails[j] = 0;
        headsPtr = &recPtr->heads[j]; // Get the address of the index of the array, one element for each thread for heads.
        tailsPtr = &recPtr->tails[j]; // Get the address of the index of the array, one element for each thread for heads.

        threads.push_back(thread([&headsPtr, &tailsPtr, timesToToss]()
        {
            for(int k=0;k<timesToToss;k++)
            { 
                if (Tosser(dre)) ++(*headsPtr); 
                else ++(*tailsPtr); 
            } 
        })); //Toss a coin!
    }

Finally, (edit 3) the lambda definition thread([&headsPtr, &tailsPtr, timesToToss]() captures pointers to headsPtr and tailsPtr, so by the time the threads get a chance to start, all threads are pointing at the headsPtr and tailsPtr of the last threads.

Hack kludge is now:

    for (int j = 0; j < recPtr->numThreads; j++)
    {

        recPtr->heads[j] = 0;
        recPtr->tails[j] = 0;
        headsPtr = &recPtr->heads[j]; // Get the address of the index of the array, one element for each thread for heads.
        tailsPtr = &recPtr->tails[j]; // Get the address of the index of the array, one element for each thread for heads.

        threads.push_back(thread([headsPtr, tailsPtr, timesToToss]()
        {
            for(int k=0;k<timesToToss;k++)
            {   
                if (Tosser(dre)) ++(*headsPtr);
                else ++(*tailsPtr);
            }
        })); //Toss a coin!
    }

I've cleaned up the formatting of the lambda to make what went wrong easier to read.

user4581301
  • 33,082
  • 7
  • 33
  • 54