所以,我正在尝试制作一个简单的多线程程序,用于验证Collatz猜想中的大量数字并返回已验证数字的总量。每个线程(总共4个)执行数字间隔,并在数字达到1时更新“validated”变量。我还计时整个过程(与单线程计算进行比较)
我遇到的问题是,当我在程序结束时打印出“validated”int时,从来没有任何一致性,所以我猜测线程是相互写入,还是主线程在其他人之前完成,因此打印不完整的数字。我还假设如果主线程在其他线程之前完成,那么clock()计算也将关闭。那么,如何停止主线程继续直到其他线程完成(从而使其等待更新的验证计数并完成准确的时间测量)?这就是我认为WaitForSingleObject所做的,但我猜它只是从EXITING中停止主线程,仍允许它计算其他函数。
这是我第一次尝试多线程,我认为我不太了解同步和WaitForSingleObject命令的工作原理。以下是我目前在主要功能中所拥有的内容:
编辑:这是我更新的主要功能和Collatz功能。我修改了它,以便每个线程访问一个单独的变量以避免同步问题,但问题仍然存在。打印出“已验证”*
时,没有一致的值再次编辑:好吧,所以我删除了每个MladenJanković的“thread”int,并且只使用一个简单的计数器将不同的间隔分配给创建的线程。现在有一个一致的,正确的“验证”值。但是,当有1,000,000个数字时,我仍然无法让程序真正完成。对100,000或甚至10,000的测试工作完美无瑕,但当我将其增加到1,000,000个数字时,程序无限期地(小时)运行而不实际返回值。我猜它正陷入特定的价值(例如马丁詹姆斯指出的750831)。我尝试用int替换long int,但似乎它仍然存在溢出。有什么建议?并感谢您的巨大帮助。
最后编辑:好吧,所以我只使用long long而不是int,现在程序运行完美。感谢大家的帮助!
void main()
{
clock_t start;
clock_t finish;
unsigned int thread = 0;
start = clock();
HANDLE h1 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, NULL, 0, NULL);
HANDLE h2 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, NULL, 0, NULL);
HANDLE h3 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, NULL, 0, NULL);
for (int i = 750001 ; i <= 1000000 ; i++) { collatz(i, 4); }
WaitForSingleObject( h1, INFINITE );
WaitForSingleObject( h2, INFINITE );
WaitForSingleObject( h3, INFINITE );
finish = clock() - start;
double time = finish / (double) CLOCKS_PER_SEC;
validated = v1 + v2 + v3 + v4;
cout << validated << " numbers validated." << endl;
cout << endl << time << " seconds." << endl;
}
unsigned _stdcall collatz_thread (void* n)
{
selection++; // selects a different interval each time collatz_thread is called
switch (selection) {
case 1:
for (int i = 1 ; i <= 250000; i++) { collatz(i, 1); }
break;
case 2:
for (int i = 250001 ; i <= 500000; i++) { collatz(i, 2); }
break;
case 3:
for (int i = 500001 ; i <= 750000; i++) { collatz(i, 3); }
break;
}
return 0;
}
int collatz (int n, int thread)
{
int original = n;
while (n != 1) {
if (n%2 == 0)
n = (n/2);
else
n = (3*n + 1);
}
if (n == 1) {
switch (thread) {
case 1:
v1++;
break;
case 2:
v2++;
break;
case 3:
v3++;
break;
case 4:
v4++;
break;
}
return n;
}
}
答案 0 :(得分:1)
如果它是共享变量,则需要同步validated
的访问权限。简单的方法是当你想增加它时使用InterlockedIncrement
函数而不是标准++
运算符。另一种方法是在访问共享变量时使用某种同步对象(如spinlock或mutex),但如果只需要同步增量操作,那就太过分了。
如果您想了解更多详情,请提供collatz
功能代码。
正如'usr'建议的那样,为了获得更好的性能,您可以在每个线程之前使用单独的变量,然后在主线程中对它们求和。在这种情况下,您应该以这种方式填充这些变量,以便它们不共享相同的缓存行以避免false sharing。
您尚未提供collatz_thread
功能,这可能是导致结果不一致的另一个原因。原因是您将指针传递给存储线程#的变量(&thread
),该线程在创建新线程的调用之间发生变化,因此根据操作系统调度程序的状态,新线程可能不会有机会在thread
变量已被更改为具有另一个值时启动,因此您将有多个线程执行相同的数据集,而其他集可能会被跳过。由于行为取决于线程调度程序的当前状态,因此几乎无法预测。
解决方案是将thread
变量转换为void*
而不是传递其地址,然后在collatz_thread
函数中将其转换回int
:
HANDLE h1 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, (void*)thread, 0, NULL);
正如Martin建议的那样,你可能有整数溢出,但它不应该导致不一致的结果,只是错误的结果,但仍然是一致的。
答案 1 :(得分:1)
试着看看这个:
Semaphore and threads explenation from MSDN
这可能是您在网上找到的最佳文档。
现在,关于你的代码,我认为它不能很好地工作,这就是为什么: WaitForSingleObject - 基本上意味着您尝试在h1信号量(或h2或h3)上执行-1,如果您不能执行-1(即信号量为0),则等待无限时间。 实际上应该在线程例程中调用WaitForSimgleObject,而不是在你的主程序中。
此外,在你的线程对象中,一旦你完成共享变量的工作,你需要释放信号量,以便其他线程可以锁定该特定的信号量。
尝试查看我给你的链接上的示例,我相信你会让它工作得很好。
答案 2 :(得分:1)
我接受了这个并且得到了一些好的,太好的结果:((某些地方出了问题,但是我没有看到它。我的运行时间不是几小时,即使是n从1到10,000,000,(一千万):
8 tests,
8 tasks,
counting to 1000000,
using 14 threads:
Validated: 1000000 in 670ms
Validated: 1000000 in 671ms
Validated: 1000000 in 624ms
Validated: 1000000 in 656ms
Validated: 1000000 in 655ms
Validated: 1000000 in 655ms
Validated: 1000000 in 640ms
Validated: 1000000 in 686ms
Average time: 657ms
Total validated: 8000000
8 tests,
8 tasks,
counting to 10000000,
using 14 threads:
Validated: 10000000 in 8081ms
Validated: 10000000 in 7441ms
Validated: 10000000 in 7784ms
Validated: 10000000 in 7598ms
Validated: 10000000 in 7956ms
Validated: 10000000 in 7534ms
Validated: 10000000 in 7816ms
Validated: 10000000 in 7769ms
Average time: 7747ms
Total validated: 80000000
请注意,尽管它表示14个线程,但它适用于整个池。一个线程总是用完等待其他任务完成,因此实际上只有13个线程可用于运行验证。出于某种原因,这是最佳的。
好吧,我的i7在所有4/8核心上都很平坦,但我看不到几小时的运行时间缩短到几秒钟,因为我有更多内核并将工作分拆给所有人:(
这就是我用的。这与你做的方式有点不同'因为我有大多数工具/代码。它是Visual C ++,首先是。有两个班。每个运行由“PoolTest”类管理,该类将几个“TestTask”实例提交给线程池,一个用于要验证的整个整数范围的每个段。您会注意到您的代码被复制/粘贴到TestTask类中。我在PoolTest代码中突出显示了TestTask的组装位置。然后'PoolTest'类等待一个Event来完成所有'TestTask'实例 - 它可以这样做,因为TestTask在完成时回调到PoolTest'taskComplete'方法。此方法访问一个线程安全的计数器,该计数器在提交TestTasks时计数,并通过'taskComplete'方法计数。
我重复使用的这段代码使事情变得复杂,因为它可以多次重复运行以获得平均时间,因此可以多次发出完整的TestTasks集。
当最后一次TestTask倒计时到零时,它会发出事件信号,然后PoolTest将再次运行,表示整个测试运行完成到GUI,(不打扰列出GUI的东西'不相关)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace WindowsFormsApplication1
{
public class TestTask: Task{
public int validated;
public int fromVal, toVal;
public int ticks;
long collatz(long n)
{
while (n != 1)
{
if (n % 2 == 0)
n = (n / 2);
else
n = (3 * n + 1);
}
return (n);
}
public override void run(){
int index;
int localTo = toVal;
int localFrom = fromVal;
int localVal = 0;
for (index = localFrom; index < localTo; index++)
{ // if not disproved, inc the stack 'validated'
if (1 == collatz(index + 1)) localVal++;
}
validated = localVal; // put stack result into instance field,
}
public TestTask(int paramx, EventHandler OnDone)
: base(paramx, OnDone){}
};
/* a task that submits testTask instances.
*/
public class PoolTest:Task{
int FnumTasks;
int FnumTests;
int Fcount;
int FtestCount;
int taskCount;
int startTicks;
int endTicks;
int totalTicks;
EventHandler FonTaskComplete;
AutoResetEvent testCompleteEvent;
public int average;
public int testTicks;
public int Vone;
public int Vtot;
public TestTask thisTestTask;
public PoolTest(int testsNum, int tasks, int count, EventHandler taskDone,
EventHandler testDone)
: base(0, testDone)
{
FnumTests=testsNum;
FtestCount=testsNum;
FnumTasks=tasks;
Fcount=count;
Vtot = 0;
Vone = 0;
totalTicks = 0;
FonTaskComplete=taskDone; // call after each test to report ticks
testCompleteEvent= new AutoResetEvent(false);
}
void submitAtest(){ // queue up numTasks testTask instances
taskCount=FnumTasks;
startTicks = System.Environment.TickCount;
//*********************THIS IS WHERE THE RANGES AND TASKS ARE ASSEMBLED
int startNum = 0; // here, start at 0 and build up the ranges
int countIncrement=Fcount/FnumTasks; // calc. range size
int endNum=startNum+countIncrement; // and so init. start/end
TestTask newTask;
for (int i = 1; i < FnumTasks; i++) // one less than requested
{
newTask=new TestTask(0, taskComplete);
newTask.fromVal=startNum; // load in the start/end for the loop
newTask.toVal = endNum;
myPool.submit(newTask); // off it goes, see you later
startNum = endNum; // now move range up for
endNum += countIncrement; // next TestTask
}
// treat last range separately to cover div rounding when
// calculating 'countIncrement'
newTask = new TestTask(0, taskComplete); // do last one
newTask.fromVal = startNum;
newTask.toVal = Fcount;
myPool.submit(newTask); // send off the last one
}
//*****************************************************************
public override void run(){
submitAtest(); //start off the first run of testTasks
testCompleteEvent.WaitOne();
}
void taskComplete(object sender, EventArgs e){ // called when a testTask
bool finishedTasks; // instance is complete
lock (this)
{
thisTestTask = (TestTask)sender;
taskCount--; // another one down
Vone += thisTestTask.validated; // Vone - total for one run
Vtot += thisTestTask.validated; // total for all runs
finishedTasks = (taskCount == 0); // this run all done yet?
if (finishedTasks)
{
endTicks = System.Environment.TickCount; // yes, so calc. elapsed time
testTicks=endTicks-startTicks;
thisTestTask.ticks = testTicks;
totalTicks=totalTicks+testTicks;
if (0!=--FtestCount) { // done all the test runs?
thisTestTask.validated = Vone; // use this field to return run total
FonTaskComplete(thisTestTask, null); // and signal result of test
Vone = 0;
submitAtest(); // no, so send off another load
}
else{
average=totalTicks/FnumTests; // done all test runs!
testCompleteEvent.Set(); // signal all runs completed
};
}
}
}
};
}