【问题标题】:Thread-safe CreateThread?线程安全的 CreateThread?
【发布时间】:2013-05-13 07:44:27
【问题描述】:

PrintHello() 函数 pthreads 示例是线程安全的吗?我在网上找到了这类示例,但我不明白它们如何是线程安全的。另一方面,如果我在PrintHello() 函数中的代码周围添加一个互斥锁,那么该示例将不会是多线程的,因为所有线程都会排队等待前一个线程退出PrintHello() 函数。此外,将其移至类也无济于事,因为似乎 CreateThread() 不允许将成员静态声明为指向非静态函数的指针。有什么办法解决吗?

#include <WinBase.h>
#include <stdio.h>
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */

#define NUM_THREADS     500

DWORD PrintHello(LPVOID oHdlRequest)
{
   long tid;
   tid = (long)GetCurrentThreadId();

   /* randomly sleep between 1 and 10 seconds */
   int sleepTime = rand() % 10 + 1;   
   sleep(sleepTime);

   printf("Hello World! It's me, thread #%ld!\n", tid);
   return 0;
}

int main (int argc, char *argv[])
{
   /* initialize random seed: */
   srand (time(NULL));

   HANDLE threads[NUM_THREADS];
   long t;
   DWORD nThreadID;

   for(t=0; t<NUM_THREADS; t++){
      printf("In main: creating thread %ld\n", t);

      threads[t] = CreateThread(
            // Default security
            NULL, 
            // Default stack size
            0, 
            // Function to execute
            (LPTHREAD_START_ROUTINE)&PrintHello,
            // Thread argument
            NULL,
            // Start the new thread immediately
            0, 
            // Thread Id
            &nThreadID
        );    

      if (!threads[t]){
         printf("ERROR; return code from CreateThread() is %d\n", GetLastError());
         exit(-1);
      }
   }
}

【问题讨论】:

  • “线程安全”是什么意思。远离什么
  • @dlev en.wikipedia.org/wiki/Thread_safety "如果一段代码仅以保证多个线程同时安全执行的方式操作共享数据结构,那么它就是线程安全的"。在上面的简单示例中,printf 可能会回显错误的线程 ID。
  • 该定义说线程安全是“安全”的代码执行。这不是很有帮助:)。无论如何,我的观点是很难定义线程的安全意味着什么:没有崩溃?为来电者提供一致的数据?最新数据?在这种情况下,代码似乎没有操纵共享状态,所以我猜根据上面的定义它是微不足道的“线程安全”。也就是说,我鼓励你阅读这篇文章:blogs.msdn.com/b/ericlippert/archive/2009/10/19/…
  • @IngeHenriksen:这个定义过于简单化了。此外,这里的共享数据结构在哪里?另外,为什么会打印错误的ID?
  • 请注意,传递给printf 的参数是可以的,但printf 使用的来自不同调用的全局输出流未同步。为了隐藏这种效果,使用了随机睡眠。移除睡眠并增加总线程数 - 您有机会看到损坏的输出。

标签: c++ windows multithreading


【解决方案1】:

由于您包含WinBase.h,因此我假设您使用的是 MSVC。 MSVC CRT 长期以来一直支持多线程访问——事实上,当前版本的 MSVC 不再支持非线程安全的单线程 CRT。我相信 VS 2003 是支持单线程 CRT 的最后一个 MSVC 版本。

在多线程 CRT 中,函数是线程安全的,如果它们在内部访问全局数据,它们将相互同步。所以在ProcessRequest() 中执行的每个printf() 相对于其他线程中的其他printf() 调用都是原子的(实际上,锁是基于流的,所以printf() 调用相对于其他CRT 函数是原子的使用stdout)。

例外情况是,如果您使用明确记录为不带锁的 I/O 函数(因此您可以出于性能原因自行同步它们),或者如果您定义 _CRT_DISABLE_PERFCRIT_LOCKS 在这种情况下 CRT 假定所有 I/O 都将在单个线程上执行。

http://msdn.microsoft.com/en-us/library/ms235505.aspx

POSIX 做出类似的保证 printf() 将是线程安全的:

【讨论】:

  • 如果锁基于流,那么printf("%d%d",x,y) 不是完全线程安全的,因为它会两次锁定流。
  • @Dialectus:为什么会两次锁定流? printf() 锁定流,执行 I/O,然后解锁流。
  • 哦,我的错。我认为printf 正在调用其他一些较低级别的函数,它实际上会锁定流。
【解决方案2】:

代码通常不是线程安全的; printf 不正常 可重入的。 (一个实现可以添加重新进入它作为 一个附加功能,但我不知道有什么功能。)你 必须在它周围添加某种保护。 (在 Windows 下, 一个所谓的CriticalSection 就足够了。)

您还必须找到sleep 的线程安全替代方案; 我找不到任何说明它是可重入的文档 (而 Posix 变体不是),但微软似乎没有 文件重入一般。一个经典的解决方案 将是创建一个互斥锁,阻止它,然后调用 WaitForSingleObject 需要的超时时间; CreateWaitableTimerWaitForSingleObject 应该作为 好。 (正如我所说,微软的文档非常缺乏; 但是WaitForSingleObject 必须是安全的,因为它被设计为 在等待互斥锁时使用。)

还要注意,除非您加入创建的线程,否则您将 可能会在main 的末尾运行,并且该过程将 在任何线程运行之前终止。 (在下面 Windows,您可以使用WaitForSingleObjectWaitForMultipleObjects 加入。)

如果您使用标准线程,则更好的解决方案是 有一个支持它们的编译器,或者 Boost threds,如果你 不要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 2022-11-21
    • 2010-09-24
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    • 2015-06-01
    相关资源
    最近更新 更多