将数组以引用方式传递给线程存在问题

4

我正在学习线程,并找到了一些简单的例子。

我的希望是创建5个线程,每个线程将随机数分配给包含20个int的数组。最后再创建另外5个线程将这些内容重构成一个更大的包含100个int的数组。

这是我之前尝试过的一些代码。我原本希望能通过引用传递数组,但没有成功。

如果您有任何想法,请记住,我完全不懂线程。

#include <process.h>
#include <windows.h>
#include <iostream>
#include <fstream>
#include <time.h>
//#include <thread>

using namespace std;

void myThread (void *dummy );
void myThread2 (void *dummy );

int main()
{

    ofstream myfile;
    myfile.open ("coinToss.csv");

    int rNum;

    long numRuns;
    long count = 0;
    int divisor = 1;
    float holder = 0;
    int counter = 0;
    float percent = 0.0;

    int array1[1000000];
    int array2[1000000];


    srand ( time(NULL) );

    printf ("Runs (use multiple of 10)? ");
    cin >> numRuns;

    for (int i = 0; i < numRuns; i++)
    {
        _beginthread( myThread, 0, (void *) (array1) );
        _beginthread( myThread2, 0, (void *) (array2) );

    }

}

void myThread (void *param )
{
    int i = *(int *)param;

    for (int x = 0; x < 1000000; x++)
    {
        //param[x] = rand() % 2 + 1;
        i[x] = rand() % 2 + 1;
    }

}

void myThread2 (void *param )
{
    int i[1000000] = *(int *)param;

    for (int = 0; x < 1000000; x++)
    {
        i[x] = rand() % 2 + 1;
    }

}

3
你期望什么,实际上又发生了什么? - Matt K
3个回答

5

首先需要认识到的一点是:

 for (int i = 0; i < numRuns; i++)
    {
        _beginthread( myThread, 0, (void *) (array1) );
        _beginthread( myThread2, 0, (void *) (array2) );

    }
_beginthread的调用会立即返回,它们不会等待线程完成甚至开始。它只是将线程排队到操作系统的调度程序中并返回。
然而,上面的代码是main()函数的结尾。我非常怀疑在发布版本下,在整个程序退出之前,您的线程甚至没有初始化。您需要构建一种机制,使主线程在程序关闭之前等待工作线程完成其工作。这超出了SO帖子的范围,但请查看CreateEvent()WaitForMultipleObjects()
接下来,您需要了解发送给线程的stuff的生命周期和所有权语义。您正在传递指向在main()作用域中自动变量数组的指针:
 int array1[1000000];
 int array2[1000000];

一旦声明这些数组的范围退出(这里是main()),变量就会消失。把指向本地作用域变量的指针传递给工作线程几乎永远都是不正确的,如果局部作用域在线程完成之前退出,则绝不正确。
动态分配这些数组,然后将它们的所有权转移到工作线程中将解决此问题。在执行此操作时,请注意管理这些对象/数组的所有权语义。

3

1. 强制类型转换:

注意强制类型转换!

void myThread (void *param )
{
  int *i = (int *) param;
  for (int x = 0; x < 1000000; x++)
  {
    i[x] = rand() % 2 + 1;
  }
}

2. 范围:

两个线程都被启动了 numRuns 次。启动它们需要一些时间,执行它们也需要时间。在结束 main 函数前,务必要等待这些线程执行完毕。你应该通过 WaitForMultipleObjects 来监控这些线程。_beginthread() 返回一个可等待的句柄 waitable handle

具体实现如下:

int main()
{
  long numRuns;
  HANDLE hThread[MAX_WAIT_OBJECTS];

  cin >> numRuns;
  // numRuns to be smaller than MAX_WAIT_OBJECTS/2!!!!


  for (int i = 0; i < numRuns; i++)
  {
     hThread[i * 2]     = _beginthread( myThread, 0, (void *) (array1) );
     hThread[i * 2 + 1] = _beginthread( myThread2, 0, (void *) (array2) );
     // or better use _beginthreadex(...)
  }
  WaitForMultipleObjects(numRuns * 2, hThread, TRUE, INFINITE);
  // bWaitAll flag set TRUE causes this statement to wait until all threads have finished.
  // dwMilliseconds set to INFINITE will force the wait to not timeout.
}

这样一来,只有当所有的线程完成任务后,main函数才会结束。

3. 访问:

这两个数组都在main部分声明,因此线程们是在共享它们。为了保护访问,您应该引入某种互斥机制。最简单的方法是使用临界区对象。当所有的线程2只访问array2,所有的线程1只访问array1时,我建议使用2个临界区对象。一个临界区同时只能被一个线程访问。

实现:

CRITICAL_SECTION cs1,cs2; // global

int main()
{
  long numRuns;
  HANDLE hThread[1000];

  // critical section object need to be initialized before aquired
  InitializeCriticalSection(&cs1);
  InitializeCriticalSection(&cs2);

  cin >> numRuns;

  for (int i = 0; i < numRuns; i++)
  {
    hThread[i * 2]     = _beginthread( myThread, 0, (void *) (array1) );
    hThread[i * 2 + 1] = _beginthread( myThread2, 0, (void *) (array2) );
  }
  WaitForMultipleObjects(numRuns * 2, hThread, TRUE, INFINITE);
}

并且这些线程(仅展示其中一个):

void myThread1 (void *param )
{
  EnterCriticalSection(&cs1); //aquire the critical section object
  int *i = (int *) param;
  for (int x = 0; x < 1000000; x++)
  {
    i[x] = rand() % 2 + 1;
  }
  LeaveCriticalSection(&cs1); // release the critical section object
}

然而: 整个故事还有点不清楚。您希望一些线程同时从开头到结尾填充相同的数组。这听起来很奇怪。使用此处所述的关键部分对象,线程将无论如何一个接一个地执行。您究竟想要什么?


谢谢关于循环的信息。我仍然需要知道如何通过引用传递数组,然后我会考虑等待结束。 - thistleknot
请注意我所描述的强制转换。您还可以查看此答案以获取更多见解:https://dev59.com/307Sa4cB1Zd3GeqP69NG - Arno
谢谢,我会尝试的。你使用的是全局变量吗?那些是线程的变量持有者吗?这是为了避免主线程杀死子线程吗? - thistleknot
你能提供一个动态分配数组的例子吗? - thistleknot
我希望用4-5个线程填充一个包含100-10000个项目的数组。这样,当我进行1万亿次计算时,速度会提高4倍。但是你的意思是每个线程都是依次运行的? - thistleknot
请重新阅读您的问题。您正在尝试运行numRuns个线程,每个线程都会将相同的数组从0到1000000进行填充。您可以将数据分割以允许不同线程同时访问,或者您需要接受只能由一个线程一次访问数据的事实。建议:提供更多关于您想要观察的细节。 - Arno

1
其他答案已经涵盖了主要的观点:还有一个问题经常会出现(至少在LINUX上,不确定Windows是否也存在)。
int array1[1000000];
int array2[1000000];

声明非常大的数组作为局部变量会存在栈空间溢出的风险,从而导致程序崩溃。

谢谢你提供关于堆栈问题的信息,这种情况我也遇到过几次。 - thistleknot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接