【问题标题】:multithreading and parameters mixup多线程和参数混淆
【发布时间】:2015-10-20 09:04:02
【问题描述】:

我有一个函数,当被单个线程调用时(直接调用它,或者通过 CreateThread() / WaitForSingleObject() 调用),它的行为正确,但是当被多个 CreateThread() 调用时,它似乎变得混乱,然后是WaitForMultipleObject() 调用。 从我尝试过的大量调试来看,似乎某些作为参数传递给被调用的主函数的变量并未在不同线程之间保持隔离,而是使用相同的地址空间(下面的示例)。以下是问题的一些细节摘要:

首先,我定义一个类型来保存每个线程需要调用的函数的所有参数:

typedef struct {
tDebugInfo DebugParms; int SampleCount; double** Sample; double** Target; double** a; double** F; double** dF; double** prevF; double** prevdF; double*** W; double*** prevW; double*** prevdW; double* e; double* dk; double* dj; double* dj2; double* sk; double* sk2; double* adzev21; double* prevadzev21; double** UW10; double* ro10e; double** dW10d; double** A; double** B; double** C; double** D; double** E; double** G; double** ET; double** AB; double** ABC; double** ABCD; double** ABCDE; double** ABCDH; double** ABCDHG; double** SABCDE; double** SABCDHG; double** I; double** J; double** M; double** x; double** xT; double* xU; double** dW10; int DataSetId; int TestId; int PredictionLen; double* Forecast; double ScaleM; double ScaleP; NN_Parms* ElmanParms; int DP[2][10];} tTrainParams;

然后我分配一个结构数组来保存每个线程的参数集:

HANDLE* HTrain = (HANDLE*)malloc(DatasetsCount*sizeof(HANDLE));
tTrainParams* tp = (tTrainParams*)malloc(DatasetsCount * sizeof(tTrainParams));
DWORD tid = 0; LPDWORD th_id = &tid;

然后,我为每个线程设置函数参数:

tp[d].ElmanParms = pElmanParams; tp[d].SampleCount = SampleCount; tp[d].Sample = SampleData_Scaled[d]; tp[d].Target = TargetData_Scaled[d]; tp[d].a = a; tp[d].F = F; tp[d].dF = dF; tp[d].prevF = prevF; tp[d].prevdF = prevdF; tp[d].W = W; tp[d].prevW = prevW; tp[d].prevdW = prevdW; tp[d].e = e; tp[d].dk = dk; tp[d].dj = dj; tp[d].dj2 = dj2; tp[d].sk = sk; tp[d].sk2 = sk2; tp[d].adzev21 = adzev21; tp[d].prevadzev21 = prevadzev21; tp[d].UW10 = UW10; tp[d].ro10e = ro10e; tp[d].dW10d = dW10d; tp[d].A = A; tp[d].B = B; tp[d].C = C; tp[d].D = D; tp[d].E = E; tp[d].G = G; tp[d].ET = ET; tp[d].AB = AB; tp[d].ABC = ABC; tp[d].ABCD = ABCD; tp[d].ABCDE = ABCDE; tp[d].ABCDH = ABCDH; tp[d].ABCDHG = ABCDHG; tp[d].SABCDE = SABCDE; tp[d].SABCDHG = SABCDHG; tp[d].I = I; tp[d].J = J; tp[d].M = M; tp[d].x = x; tp[d].xT = xT; tp[d].xU = xU; tp[d].dW10 = dW10; tp[d].DebugParms = pDebugParms; tp[d].ElmanParms = pElmanParams; tp[d].PredictionLen = pPredictionLen; tp[d].Forecast = ForecastData[d]; tp[d].ScaleM = ScaleM[d]; tp[d].ScaleP = ScaleP[d]; tp[d].TestId = pTestId; tp[d].DataSetId = d;

然后,我为每个线程调用一个包装函数GetForecastFromTraining(tTrainParams* parms),并在“tp”结构体数组中预先设置了相关参数:

HTrain[d] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GetForecastFromTraining, &tp[d], 0, th_id);

最后,我调用了 WaitForMultipleObjects():

WaitForMultipleObjects(DatasetsCount, HTrain, TRUE, INFINITE);

GetForecastFromTraining() 对于大多数变量(显然仅限于数组)内部发生的情况是,每当一个线程更改一个数组元素的值(例如,W[0][0][0])时,新值在内部变为当前值所有其他线程也是如此。当然,这会搞砸所有线程中正在进行的所有计算,并且在我看来与跨线程的整个隔离故事相反。

其中一个提示是,当我查看 VS2013 中的“Parallel Watch”调试窗口时,我看到 W 在所有线程中具有相同的地址(因此具有相同的值);但是,&W 对于每个线程都是不同的。其他非数组变量似乎表现良好。最后,我仔细检查了编译器选项中的 /MTd 标志,它就在那里。

我对此很迷茫。有什么建议吗?

P.S.:这是我的程序的简化版本,它显示了相同的问题行为。在此示例中,在 Sleep(1000) 行之后中断执行表明 a1、a2 和 G 变量每个都正确包含线程 id,而 F 对于所有线程都是相同的。

#include <Windows.h>
#include <stdio.h>

#define MAX_THREADS 5
HANDLE h[MAX_THREADS];

typedef struct{
    int a1;
    int a2;
    double* F;
    double G[5];
} tMySumParms;

void MySum(tMySumParms* p){
    int tid = GetCurrentThreadId();
    Sleep(200);
    p->a1 = tid;
    p->a2 = -tid;
    p->F[0] = tid;
    p->F[1] = -tid;
    p->G[0] = tid;
    p->G[1] = -tid;
    Sleep(1000);
}

extern "C" __declspec(dllexport) int GetKaz(){
    LPDWORD t = NULL;
    tMySumParms* p = (tMySumParms*)malloc(MAX_THREADS*sizeof(tMySumParms));
    HANDLE* h = (HANDLE*)malloc(MAX_THREADS*sizeof(HANDLE));
    double G[5];
    double* F = (double*)malloc(5 * sizeof(double)); 

    for (int i = 0; i < MAX_THREADS; i++){
        p[i].a1 = 1;
        p[i].a2 = 2 ;
        p[i].F = F;
        memcpy(p[i].G, G, 5 * sizeof(double));
        h[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MySum, &p[i], 0, t);
    }
    WaitForMultipleObjects(MAX_THREADS, h, TRUE, INFINITE);

    return 0;
}

【问题讨论】:

  • 您必须显示设置 tp 指向的数据的代码。我的猜测是你的初始化有点搞砸了,最后所有不同的 Ws 实际上都引用了相同的内存。
  • 我放弃了水平滚动挑战。即使没有这个,你省略了这么多代码的事实意味着我们必须猜测。格式良好的minimal reproducible example 可以让别人帮助你。
  • 我刚刚编辑了我的问题,添加了设置 tp 参数的行

标签: c arrays multithreading winapi


【解决方案1】:

W 在参数结构中声明为double***,稍后在问题中您说您将其用作W[0][0][0]。所以W 是一个指向双精度数组的指针数组的指针数组。
我的猜测是这些层之一对所有线程都是通用的。

为了证实这个理论,并确保它不是并发问题而是数据结构问题,我将创建一个简单的单线程测试函数,如下所示:

  • 1.0 填充用于线程 1 的数组
  • 然后用2.0填充线程2的数组
  • 检查线程 1 的值。

精简版显示了问题:F 数组被分配一次,每个线程都得到一个指向这个数组的指针。因此,如果一个线程更新数组,所有其他线程都会看到更改。

double* F = (double*)malloc(5 * sizeof(double));    // one array!
for (int i = 0; i < MAX_THREADS; i++){
    ...
    p[i].F = F;                     // all threads use the same array!

改成:

for (int i = 0; i < MAX_THREADS; i++){
    ...
    p[i].F = malloc(5 * sizeof(double)); // each thread has its own array

【讨论】:

  • 正如我在开头所写的,当从单个线程调用时,通过使用相同的 CreateThread() 调用并将 WaitForMultipleObjects() 替换为 WaitForSingleObject(),整个过程工作正常。所以,我相信数据结构设置正确。我在调试器中使用 W[0][0][0] 来检查所有线程中 W[0][0][0] 的值。
  • 如果W 用于保存计算的临时值,它可以在串行完成时工作,因为它们是否常见无关紧要。
  • 好的,我做了更多的测试,看起来你可能走在正确的轨道上(下面的完整代码):a1 和 a2 更新在线程之间有效地保持隔离,而 W 是不是(即在每个线程的更新中,值在线程之间发生变化)。那么,我的问题是,当作为参数传递给可能由不同线程同时调用的函数时,我应该如何声明/使用动态数组,例如 W?
  • 为每个线程制作W 的副本应该可以工作。也许有一个开销较小的解决方案,但是如果不看所有代码就很难说。最好在问题中发布代码(如果代码很多),否则使用反引号。
  • 谢谢。最后,我不得不为 W 添加一个额外的维度,并为每个线程分配单独的 3 维 W。这似乎可以解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-08
  • 1970-01-01
  • 2012-10-04
相关资源
最近更新 更多