【问题标题】:Critical section usage in multithreading?多线程中的关键部分使用?
【发布时间】:2012-09-11 05:06:33
【问题描述】:

我在网上找到了这个关于多线程和下载代码的教程,以便在 Visual Studio 2010 上试用。http://www.codeproject.com/Articles/14746/Multithreading-Tutorial

我在下面复制了一个与“线程本地存储”相关的程序供您参考。它看起来很简单,因为 2 个线程,都增加了类数据成员 'm1'、'm2'、'm3'。在本例中,“m2”是一个静态类变量,两个线程都可以访问。

请注意,代码中的关键部分在文件开头使用“#define WITH_SYNCHRONIZATION”启用。我的理解是,由于 'TMain()' 中的 'for 循环' 受临界区保护,因此首先到达 'TMain()' 的线程将完成 50,000 增量作为一个整体,而不会与另一个线程交错,打印输出 '50,020 for m2',另一个线程将继续其余部分并稍后打印出 '100,020 for m2'。

但是不,m2 的打印输出看起来根本没有关键部分。 'm2' 值使用以下值打乱:

线程 t2:... m2=50376 ...
线程 t1: ... m2=63964 ...

我一定错过了一些基本的东西。这是什么?

以下代码来自网页,数值变化不大,可以在VStudio中轻松编译。

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

using namespace std;

#define WITH_SYNCHRONIZATION

class ThreadX
{
private:
  int        m1;
  static int m2;                       // shared variable
  static  __declspec(thread)  int m3;  // thread local variable


#ifdef WITH_SYNCHRONIZATION
  CRITICAL_SECTION m_CriticalSection; 
#endif

public:
  string threadName;

  ThreadX()
  {
      m1 = 10;
#ifdef WITH_SYNCHRONIZATION
        InitializeCriticalSection(&m_CriticalSection);
#endif
  }

  virtual ~ThreadX()
  {
#ifdef WITH_SYNCHRONIZATION
       // Release resources used by the critical section object.
       DeleteCriticalSection(&m_CriticalSection);
#endif
  }

  void TMain(void) 
  {
#ifdef WITH_SYNCHRONIZATION
    EnterCriticalSection( &m_CriticalSection );
#endif

    for ( int i = 1; i <= 50000; i++ )
    {
        ++m1;  // init value 10
        ++m2;  // init value 20
        ++m3;  // init value 30
    }

    printf( "Thread %s: m1 = %d, m2 = %d, m3 = %d\n", threadName.c_str(), m1, m2, m3 );

#ifdef WITH_SYNCHRONIZATION
    LeaveCriticalSection( &m_CriticalSection );
#endif

  } 

  static unsigned __stdcall ThreadStaticTMain(void * pThis)
  {
      ThreadX * pthX = (ThreadX*)pThis;
      pthX->TMain();

      return 1;
  }

};

int ThreadX::m2 = 20;
int ThreadX::m3 = 30;

int main()
{
    // In this program we create 2 threads and request that their
    // entry-point-function be the TMain() function of the ThreadX
    // class.  Because _beginthreadex() cannot accept a class member
    // function we must employ a 2 step process involving a tricky
    // cast to accomplish this.

    ThreadX * o1 = new ThreadX();

    HANDLE   hth1;
    unsigned  uiThread1ID;

    hth1 = (HANDLE)_beginthreadex( NULL,         
                                   0,            
                                   ThreadX::ThreadStaticTMain,
                                   o1,           
                                   CREATE_SUSPENDED, 
                                   &uiThread1ID );

    if ( hth1 == 0 )
        printf("Failed to create thread 1\n");

    DWORD   dwExitCode;

    GetExitCodeThread( hth1, &dwExitCode );
    printf( "initial thread 1 exit code = %u\n", dwExitCode );

    o1->threadName = "t1";

    ThreadX * o2 = new ThreadX();

    HANDLE   hth2;
    unsigned  uiThread2ID;

    hth2 = (HANDLE)_beginthreadex( NULL,         
                                   0,            
                                   ThreadX::ThreadStaticTMain,
                                   o2,           
                                   CREATE_SUSPENDED, 
                                   &uiThread2ID );

    if ( hth2 == 0 )
        printf("Failed to create thread 2\n");

    GetExitCodeThread( hth2, &dwExitCode ); 
    printf( "initial thread 2 exit code = %u\n", dwExitCode );

    o2->threadName = "t2";

    ResumeThread( hth1 );   
    ResumeThread( hth2 );   

    WaitForSingleObject( hth1, INFINITE );  
    WaitForSingleObject( hth2, INFINITE );  

    GetExitCodeThread( hth1, &dwExitCode );
    printf( "thread 1 exited with code %u\n", dwExitCode );

    GetExitCodeThread( hth2, &dwExitCode );
    printf( "thread 2 exited with code %u\n", dwExitCode );

    // The handle returned by _beginthreadex() has to be closed
    // by the caller of _beginthreadex().

    CloseHandle( hth1 );
    CloseHandle( hth2 );

    delete o1;
    o1 = NULL;

    delete o2;
    o2 = NULL;

    printf("Primary thread terminating.\n");
    return 0;
}

【问题讨论】:

    标签: c++ multithreading winapi


    【解决方案1】:

    CRITICAL_SECTION m_CriticalSection;ThreadX 类中的成员(实例)变量。这意味着每次您创建ThreadX 的实例(您执行两次)时,您都在创建一个新的CRITICAL_SECTION。这没有好处,因为每个实例都将进入自己的临界区,没问题,然后继续丢弃您试图保护的变量。

    如果您查看EnterCriticalSection 的文档,您会看到它提到只创建一个CRITICAL_SECTION,每个线程都会尝试输入。

    相反,您只需要创建一个CRITICAL_SECTIONThreadX 的所有实例都将使用它。理想情况下,这将是ThreadX 中的static member variable。但是,C++ doesn't have static constructors(如 C#),因此您必须以其他方式调用 InitializeCriticalSection。您总是可以将其设为在 main() 中初始化的静态变量。

    【讨论】:

      【解决方案2】:

      m_CriticalSection 成员对于 ThreadX 类的每个实例都是不同的。因此,每个 ThreadX 对象都使用自己的临界区——这就是为什么它不会相互影响。

      【讨论】:

      • @Tutankhamen,我想说最好使用 适当范围 关键部分。如果只在ThreadX中使用,那么它应该是ThreadX中的静态字段。
      • 是的,你是对的。最好在 c-tor 和 d-tor 中使用带有引用计数的 singletoin。临界区在创建 ThreadX 的第一个副本时初始化,并在调用最后一个副本的析构函数时取消初始化。
      • 感谢图坦卡蒙。那么在这个示例程序中,'#define WITH_SYNCHRONIZATION' 有什么不同吗?那么作者使用临界区的意义何在?
      • 该定义不会影响程序行为,因为您的每个 ThreadX 实例都将使用其个人关键部分进行操作。你能用这样的例子来命名这本书吗?
      【解决方案3】:

      这是因为您为每个线程定义了一个临界区。您需要在 main 中定义一个全局临界区,并将该临界区传递给每个线程类。

      【讨论】:

      • 非常感谢。那么这个程序中的关键部分是否有任何用处?
      • 现在使用它什么也不做;每个部分只有一个用户,因此两个线程都在前进。如果你创建了一个所有线程都使用的全局部分,那么它会像你期望的那样工作,尽管这两个线程可以按任何顺序执行,如果线程 2 恰好首先到达临界区,它将进入它并停止线程 1,那么你将得到线程 2:50'020,线程 1:100'020。但反过来也同样有效。在这种简单的情况下,您得到的结果可能更多地取决于实施而不是运气。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-14
      相关资源
      最近更新 更多