多线程硬币投掷实验

时间:2016-05-05 18:08:34

标签: c++ multithreading pointers dereference dynamic-allocation

预备要点:我需要做什么改变才能在我的记录中显示正确的头/尾值?

编辑1:一旦有超过1个线程,Record内部的整数数组似乎会填充随机值。

我现在已经尝试调试这几天了。我的代码已完成并完全执行。 (大家都知道,我是一名学生而不是假装成一名专业程序员。)

在我的多线程投币程序中,我试图捕捉每个线程中出现头或尾的次数。我翻了一千次硬币共计100,000,000次。 (一亿。)

记录类中数组中的元素没有正确累积。我知道我不需要使用互斥锁,因为每个线程都在访问线程独有的独立内存位置。但线程正在不正确地更新值,这不应该发生。

以下是我的调试示例。

  1. 单线程(注意正确的头/尾数量):
  2. Debug output of single thread.

    1. 两个主题(现在头部/尾部计数只是疯了。):
    2. Debug output of two threads.

      下面是完全可执行的代码示例,然后是示例输出。此外,here也链接到Github上的完整代码:

      #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;
      }
      

      记录#2的输出是[更新时间为5/5/2016 @ 2:16 pm 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
      

1 个答案:

答案 0 :(得分:5)

int *heads;heads定义为指针。

<<的默认行为是打印指针的地址,而不是指向的数据。指向char的指针有一个例外,可以打印出c风格的字符串。

由于每个线程都有自己的int来计算线程生成的头数,当线程完成后,必须对头数进行求和并打印总和。

此外,

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

为头部计数器分配了存储空间,但我在代码中找不到任何内容来初始化它们。这是未定义的行为。一个简单的黑客修复方法是:

    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!
    }

最后,(编辑3)lambda定义thread([&headsPtr, &tailsPtr, timesToToss]()捕获指向headsPtrtailsPtr的指针,所以当线程有机会启动时,所有线程都指向最后一个帖子的headsPtrtailsPtr

Hack kludge现在是:

    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!
    }

我已经清理了lambda的格式,以便更容易阅读。