【问题标题】:Avoid race conditions when using pointers and threads使用指针和线程时避免竞争条件
【发布时间】:2019-06-28 13:39:46
【问题描述】:

我只是为了自娱自乐地修改这段代码:

#include <windows.h>
#include <process.h>
#include <stdlib.h>
#include <stdio.h>

void print_message(void *param)
{
    int thread_counter = *((int*)param);
    printf("Thread #%i\r\n", thread_counter);
    _endthreadex(0);
}

int main()
{
    unsigned thread_ID;
    HANDLE thread_handle;

    int thread_count = 10;

    HANDLE thread_handles[thread_count];

    for(int i = 0; i < thread_count; i++) 
    {
        thread_handles[i] = (HANDLE)_beginthreadex(NULL, 0, &print_message, &i, 0, &thread_ID);
        thread_handle = thread_handles[i];
    }

    WaitForMultipleObjects(thread_count, thread_handles, TRUE, INFINITE);
    return EXIT_SUCCESS;
}

一个

Output
Thread #8
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10
Thread #10

我认为问题在于我传递了对变量 i 的引用,而变量 i 在 for 循环中正在递增,而线程正在使用/操作此引用。

如何避免竞争条件,并将变量 i 的值传递给线程函数?

我也想将字符串/字符数组传递给线程函数,但我似乎不明白该怎么做。

我在 Windows 10 上使用 Tiny C 编译器 (TCC)

【问题讨论】:

  • 您无法控制每个线程何时运行。除非你想从前一个线程开始的下一个线程发出信号,它已经启动,否则你可能需要传递单独的内存。您可以使用一个数组并将指针传递给该数组中的条目。
  • 重复关于 gazillion pthread_create 问题

标签: c multithreading pointers race-condition


【解决方案1】:

不要将指针传递给i,传递存储在i本身中的

void print_message(void *param)
{
    int thread_counter = (intptr_t)param;
    printf("Thread #%i\r\n", thread_counter);
    _endthreadex(0);
}

// ... rest of code ...
    // We cast the value in i to a pointer width integer value, intptr_t
    // to explicitly match the size of the void* we're smuggling it in
    thread_handles[i] = (HANDLE)_beginthreadex(NULL, 0, &print_message, (void*)(intptr_t)i, 0, &thread_ID);
// ... rest of code ...

如果您传递的只是一个小于指针宽度值的单个值,您可以将其值作为“指针”值本身偷运进来。在这种情况下,您只需将其向上转换为 intptr_t(与指针宽度匹配的整数类型),然后在提取时向下转换。

因为传递的是实际值,而不是指向它的指针,所以在调用_beginthreadex 之前进行了复制,之后修改i 不会改变任何内容。

如果要传递的数据更大,那么您要么施加某种障碍(以确保在主线程再次接触i 之前读取值)或动态内存使用(分配空间,复制值,将分配内存的指针传递给线程,线程在释放空间之前提取值)。第三个选项是一个值数组,每个线程一个(所以第一个线程接收&amp;arr[i],其中i0,下一个&amp;arr[i] 接收i == 1,等等)。

【讨论】:

  • 很高兴我了解了intptr_tnow。我会偷它作为我的答案。 :P 但是 thread_counter 不应该也属于intptr_t吗?
  • 回复,“通过 i 本身。” IMO,当有人在努力理解指针和引用时,我们需要非常小心我们所说的话。当您说“i 本身”时,任何有经验的程序员都会立即看到您的意思是 i 的值,但对于新手来说,这并不总是那么明显。
  • @KamiKaze:鉴于原始值为int,我们已经知道将thread_counter 存储为int 是安全的。是的,它更安全(假设int 理论上可以大于intptr_t)始终使用intptr_t(让ithread_counter 从一开始就是intptr_t),但是如果iint,我认为将thread_counter 也设为int 对对称性更好。我们使用intptr_t 的事实是您必须如何在指针变量中走私整数的一种实现细节,它本身不是用于一般存储的有用类型。
  • @SolomonSlow:是的。为了清楚起见,我会调整措辞。
  • 实际上是相反的。 intptr_t 在 x64 系统上有 64 位,而 int 通常只有 32 位。所以这样你可以产生一个整数溢出,它是有符号整数的 UB。双重演员应该完成什么? (void*)(intptr_t)i
【解决方案2】:

您可以传递值而不是与此类似的指针。

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

void print(void *foo){
    intptr_t bar = (intptr_t)foo; 
    printf("%" PRIdPTR "\n",bar); 
}

int main(void)
{
    intptr_t i=1;
    print((void*)i);
    return 0;
}

您只需要小心该函数确实将其作为一个值。 在函数本身中,您必须确保您转换的变量可以采用指针的大小。 (这是通过使用intptr_t 完成的)

为了避免一般情况下的竞争条件,您可以使用互斥锁,但这在这里并不是很有用。

您可以以某种方式发出信号表明该值已在线程中读取,然后让循环等到那时,但这会使线程变得毫无用处。

【讨论】:

    【解决方案3】:

    如果您想在多个线程之间共享变量,请查看互斥锁:https://www.thegeekstuff.com/2012/05/c-mutex-examples/

    互斥锁允许你“阻塞”一个变量,这样一次只有一个线程可以使用它。

    【讨论】:

    • 标准互斥锁使用(如您的链接所示)在这里无济于事;它不会阻止 main 线程在被子线程读取之前修改i(即使主线程在修改i 之前也被锁定,也不能保证子线程会得到先锁定)。有一些方法可以解决这个问题(所以主线程在子线程指示它读出值之前不会继续),但这会引入额外的开销并减慢子线程可以启动的速度(并且没有说明您的链接在任何情况下)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 2010-09-25
    • 2017-12-02
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多