【问题标题】:Method of Passing Thread Parameters Causing Race Condition?传递线程参数导致竞争条件的方法?
【发布时间】:2015-03-24 03:16:06
【问题描述】:

我有一个关于我正在编写的程序的问题,它需要一些多线程。线程函数需要几个参数,我通过以下方式传递:

  1. 为参数定义了一个结构(我们称之为 ThreadDataStruct)
  2. 初始化此结构并填充值(调用我们的 ThreadDataStruct 实例 THREAD_DATA)
  3. 通过将 lpParameter 设置为指向结构 (&THREAD_DATA) 的指针,在 CreateThread 期间将值传递给我的线程
  4. 在线程函数中将 LPVOID 重新转换为 ThreadDataStruct

问题是我创建线程的函数在调用 CreateThread 后直接返回。我的问题是:创建线程后这么快就返回会导致线程无法获取其参数吗?如果结构体是在函数中创建的,然后函数返回,线程函数的参数如果不能足够快地获取,会不会被销毁?

这里有一些 POC 来说明我的意思:

我们的结构定义:

typedef struct ThreadDataStruct
{

    int Number;
};

我们在里面运行的函数:

void Function()
{

    ThreadDataStruct THREAD_DATA;
    THREAD_DATA.Number = 1;

    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunction, &THREAD_DATA, 0, NULL);
    return;
}

我们的线程函数:

void ThreadFunction(LPVOID lpParam)
{

    ThreadDataStruct THREAD_DATA = *(ThreadDataStruct *)lpParam;
    int NumberFromStruct = THREAD_DATA.Number;

    return;
}

我是否需要另一种方法来安全地传递参数?

【问题讨论】:

  • 是的,你有一个竞争条件;是的,您需要有一种不同的方式将参数安全地传递给函数。传递给线程函数的变量(通常)是按地址传递的,因此只要线程需要访问它,它就需要持续存在。在您的代码中,函数返回,这意味着该变量不再有效,所以一切都乱套了。
  • @JonathanLeffler 感谢您澄清这一点。有哪些方法可以安全地传递参数?我想我可以在 CreateThread 之后放置一个 Sleep(5000),但这显然不是最好的方法。
  • 一种选择是让Function() 等待线程在它返回之前完成。另一种选择是让Function() 等待一个信号量,当它完成读取其控制数据时,ThreadFunction() 会在信号量上发出信号。另一种选择,可能是最好的,是让调用Function() 的函数传入一个可以使用的ThreadDataStruct *,并让它保持这个状态直到所有线程都终止。 [...继续...]
  • […continuation…] 所以,如果是 main() 调用 Function(),那么 main() 可能有一个 ThreadDataStruct thread_params[32]; 数组,它可以通过&thread_params[i]ith 调用到 Function() (可能在一个循环内)。毫无疑问,还有其他等效技术可用;这些立即浮现在脑海中。但是,通常,控制代码具有某种可用的数据结构数组,并将该数组的元素之一传递给线程启动函数的每次调用。
  • @JonathanLeffler 感谢您的帮助

标签: c multithreading


【解决方案1】:

将 cmets 转换为答案。

是的,你有一个竞争条件;是的,您需要有一种不同的方式将参数安全地传递给函数。传递给线程函数的变量(通常)是按地址传递的,因此只要线程需要访问它,它就需要持续存在。在您的代码中,函数返回,这意味着该变量不再有效,所以一切都乱套了。

感谢您澄清这一点。有哪些方法可以安全地传递参数?我想我可以在CreateThread() 之后加上Sleep(5000),但这显然不是最好的方法。

一种选择是让Function() 等待线程完成后再返回。另一种选择是让Function() 等待一个信号量,当它完成读取其控制数据时,ThreadFunction() 会在信号量上发出信号。

另一种选择,可能是最好的选择,是让调用Function() 的函数传入一个可以使用的ThreadDataStruct *,并让它保持这个状态直到所有线程都终止。所以,如果是main() 调用Function(),那么main() 可能有一个ThreadDataStruct thread_params[32]; 数组,它可以将&thread_params[i] 传递给ith 调用@987654334 @(可能在循环中)。

void Function(ThreadDataStruct *THREAD_DATA)
{
    THREAD_DATA->Number = 1;

    CreateThread(NULL, 0, ThreadFunction, THREAD_DATA, 0, NULL);
}

int main(void)
{
    ThreadDataStruct thread_params[32];

    for (int i = 0; i < 32; i++)
        Function(&thread_params[i]);

    …code to wait for threads to exit…
}

Function() 的变化包括:

  • 添加参数。
  • 通过指针初始化参数。
  • 直接传递参数,而不是&amp;THREAD_DATA
  • 不要强制转换线程函数指针类型;确保ThreadFunction()CreateThread() 调用的正确类型。
  • return;void 函数的末尾没有任何优点。

毫无疑问,还有其他等效技术可用;这些立即浮现在脑海中。但是,通常,控制代码具有某种可用的数据结构数组,并将该数组的元素之一传递给线程启动函数的每次调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-20
    • 2016-10-12
    • 2021-06-04
    • 1970-01-01
    相关资源
    最近更新 更多