多线程抛硬币实验
Multi-threaded Coin-toss Experiment
前面的要点:我需要做什么更改才能在我的记录中显示 heads/tails 的正确值?
编辑 1:当线程超过 1 个时,Record 中的整数数组似乎填充了随机值。
几天来我一直在尝试调试它。我的代码已完成并完全执行。 (顺便说一下,我是学生,不是专业程序员。)
在我的多线程抛硬币程序中,我试图捕获每个线程中出现正面或反面的次数。我总共掷了 100,000,000 次硬币。 (一亿。)
我的记录 class 中数组中的元素没有正确累积。我知道我不需要使用互斥锁,因为每个线程都在访问线程独有的单独内存位置。但是线程错误地更新了值,这不应该发生。
以下是我的调试示例。
- 单线程(注意 heads/tails 的正确数量):
- 两个线程(现在 heads/tails 计数变得疯狂。):
下面是完全可执行的代码示例,后面是示例输出。此外,here 也是 link 到 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;
}
recored #2 的输出是[更新于 2016 年 5 月 5 日@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
int *heads;
定义 heads
为指针。
<<
的默认行为是打印指针的地址,而不是指向的数据。指向 char
的指针打印出 C 风格的字符串是一个例外。
由于每个线程都有自己的 int
来计算线程生成的磁头数,当线程完成时,必须对磁头计数求和并打印总和。
此外,
recPtr->heads = new int[recPtr->numThreads];
为头部计数器分配了存储空间,但我在代码中找不到任何东西来初始化它们。这是未定义的行为。一个简单的 hack 修复是:
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]()
捕获指向 headsPtr
和 tailsPtr
的指针,因此当线程有机会启动时,所有线程都指向在最后一个线程的 headsPtr
和 tailsPtr
处。
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 的格式,使出错的地方更容易阅读。
前面的要点:我需要做什么更改才能在我的记录中显示 heads/tails 的正确值?
编辑 1:当线程超过 1 个时,Record 中的整数数组似乎填充了随机值。
几天来我一直在尝试调试它。我的代码已完成并完全执行。 (顺便说一下,我是学生,不是专业程序员。)
在我的多线程抛硬币程序中,我试图捕获每个线程中出现正面或反面的次数。我总共掷了 100,000,000 次硬币。 (一亿。)
我的记录 class 中数组中的元素没有正确累积。我知道我不需要使用互斥锁,因为每个线程都在访问线程独有的单独内存位置。但是线程错误地更新了值,这不应该发生。
以下是我的调试示例。
- 单线程(注意 heads/tails 的正确数量):
- 两个线程(现在 heads/tails 计数变得疯狂。):
下面是完全可执行的代码示例,后面是示例输出。此外,here 也是 link 到 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;
}
recored #2 的输出是[更新于 2016 年 5 月 5 日@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
int *heads;
定义 heads
为指针。
<<
的默认行为是打印指针的地址,而不是指向的数据。指向 char
的指针打印出 C 风格的字符串是一个例外。
由于每个线程都有自己的 int
来计算线程生成的磁头数,当线程完成时,必须对磁头计数求和并打印总和。
此外,
recPtr->heads = new int[recPtr->numThreads];
为头部计数器分配了存储空间,但我在代码中找不到任何东西来初始化它们。这是未定义的行为。一个简单的 hack 修复是:
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]()
捕获指向 headsPtr
和 tailsPtr
的指针,因此当线程有机会启动时,所有线程都指向在最后一个线程的 headsPtr
和 tailsPtr
处。
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 的格式,使出错的地方更容易阅读。