多线程和参数混合

时间:2015-10-20 09:04:02

标签: c arrays multithreading winapi

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

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

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])时,新值变为当前内部所有其他线程也是如此。当然,这会搞砸所有线程中的所有计算,并且我认为这与跨线程的整个隔离故事相悖。

当我看到" Parallel Watch"在VS2013里面调试窗口,我看到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;
}

1 个答案:

答案 0 :(得分:1)

W在参数struct中声明为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