【问题标题】:Non blocking thread synchronization非阻塞线程同步
【发布时间】:2013-08-12 22:26:09
【问题描述】:

我有一个带有按钮的对话框和一些其他控件。

当按下该按钮时,会产生一个工作线程。

为了便于讨论,我们只说线程函数做了很长的工作。

每次单击按钮时,都会产生新线程并执行其工作。

在工作线程执行其工作时不应阻止对话框,因为用户应该能够将其最小化,单击其他控件等等。

在维基百科上,我找到了一个术语无锁算法,它指的是非阻塞线程同步。

这就是我对非阻塞线程同步感兴趣的原因。我相信这将确保我需要的行为。

我是多线程的新手,但我确实在这里找到了一些关于它的文章/已回答的问题,并且已经阅读了有关它的 Microsoft 文档。

其中大多数使用以下算法:

1.

主线程声明一个 volatile 变量(通常是 int 或 bool )并将其传递给工作线程。工作线程在循环中检查变量,如果未设置为指示其终止,则继续执行其工作。当需要终止时,父线程设置变量。

2.

Microsoft 网站上还有大量关于同步的文档。在那里,我发现了互斥锁、临界区、互锁等等。

这些问题是它们总是阻塞父线程(通常使用 WaitForSingleObject 或 WaitForMultipleObjects API),直到工作线程完成。

此外,在这里搜索,我发现了一个帮助我开始的最近的问题 (Abort thread properly when dialog box close button is clicked)。

为了解决我的问题,我将把这个问题分成多个,分别发布,以尊重 StackOverflow 的规则。

所以我现在的第一个问题是:

我应该使用哪种 API/算法来实现非阻塞线程同步,这将有助于我实现对话框的行为如上所述?

如果可能的话,我还希望提供指向教程或代码示例的链接,因为我在 Google 上不走运(此外,开发人员通过代码/伪代码学得最好)。

我将在下面使用这些代码 sn-ps 展示我的初步尝试,以防它们被证明是有用的:

   // thread function

   DWORD WINAPI MyThread()
   {
       int result = 0;

       // do something

       if( /** everything is OK **/ )
           return result;
       else
       {
           result = 1; 

           return result;
       }
   }

现在对话框:

  // button clicked handler

  case IDC_BUTTON1:
        {
            // create thread

            DWORD tID;

            HANDLE th = CreateThread( NULL , 0 , 
                        (LPTHREAD_START_ROUTINE)MyThread , 
                        NULL , 0 , &tID );

            if( !th )
                EndDialog( hWnd, IDCANCEL );

            CloseHandle( th );
         }
         break;

   case IDCANCEL:

         EndDialog( hWnd, IDCANCEL );

         break;

此时,当我运行程序时,激活对话框,然后单击按钮,工作线程就会产生,如果我等待它完成就好了。

但是,如果我提前关闭对话框,我的问题的性质与我在此处找到的问题相同(我认为 IDCANCEL 处理程序中的 WaitForMultipleObjects 可以部分解决此问题,但我将把它留到另一篇文章中)。

编辑#1:

我发布了新代码,以反映我的进度和遇到的问题。

重要提示:

我没有启动多个线程,而是隐藏了启动线程的按钮,一旦按下它。

这意味着用户必须等待第一个线程完成,然后才能激活第二个线程。

这样做是为了便于调试。

定义自定义消息以指示线程是否正常退出或发生错误。

    #define WM_THREAD_OK      ( WM_APP + 1 )

    #define WM_THREAD_ERROR   ( WM_APP + 2 )

添加将传递给线程的数据结构,以便它可以与对话框通信,因此可以正确中止

    struct Data
    {
        HWND hwnd;

        bool bContinue;
    };

重新设计的线程函数将消息发送到对话框,以便它可以通知对话框有关错误或优雅结束。

新代码(这是基于 Charles Petzold-Programming Windows 5th ed. 书中的示例。):

   // thread function

   DWORD WINAPI MyThread( LPVOID lpvoid )
   {

       HRESULT hr;

       volatile Data *data = ( Data* )lpvoid;

       try
       {
          for( int i = 0; i < 10 && data->bContinue; i++ )
          {
              hr = // do something

              if( FAILED(hr) )
                  throw _com_error(hr);
          }

          // once you leave loop, check if thread was aborted, or all was well

          if( ! ( data->bContinue ) )
          {
              // thread was aborted, do cleanup and exit

              // cleanup

              return 0;
          }

          // if all went well, do cleanup, and "say" so to the dialog box

          // do cleanup here

          SendMessage( data->hwnd, WM_THREAD_OK, 0, 0 );

          return 0; // exit gracefully
       }
       catch( _com_error & )
       {
           // do cleanup

           SendMessage( data->hwnd, WM_THREAD_ERROR, 0, 0 );

           return 1;
       }
   }

这是对话框的新代码(注意:对话框现在是无模式的):

  static Data data;

  HANDLE th = NULL;

  // button clicked handler

  case IDC_BUTTON1:
        {
            // create thread

            DWORD tID;

            th = CreateThread( NULL , 0 , 
                        (LPTHREAD_START_ROUTINE)MyThread , 
                        (LPVOID)&data, 0 , &tID );

            if( !th )
                DestroyWindow( hwnd );

            // hide the button which activates thread

            ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_HIDE );
         }
         break;

   case WM_THREAD_OK:

     if( th )
         CloseHandle( th );

         ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_SHOW );

     break;

   case WM_THREAD_ERROR:

       if( th )
           CloseHandle( threadHandle );

       MessageBox( hwnd, L"Error", L"Error", MB_ICONERROR );

       break;

   case IDCANCEL:

         data.bContinue = false; // set thread abortion flag

         if( th )
         {
            // after I comment out below statement, thread aborts properly

            // WaitForSingleObject( th, INFINITE );

            CloseHandle( th );
         }

         DestroyWindow( hwnd ); // dialog box is now modeless one!

         break;

我的问题是:这段代码有错误吗?是否可以改进,如何改进?

谢谢。

【问题讨论】:

  • 参见 MSDN 上的 PostMessage()。 0-wait answer/s 的轮询是没有希望的。
  • 有很多方法可以做到这一点。在调用EndDialog() 之前等待all 线程句柄将是最直接的解决方案。您目前在创建线程后立即关闭您的线程句柄,因此必须进行更改(但您仍想关闭它们,只需在收到信号后终止)。将每个线程保持在一个连续的区域(例如 std::vector&lt;HANDLE&gt; 可能会让您更轻松(如果您使用 WaitForMultipleObjects(),则需要这样做,但同样,不要忘记在 Win32 端进行适当的清理。
  • 谢谢。你提到有很多方法可以做到这一点,所以我问你这个:你能提供一些小的sn-p/好的教程/算法,可以帮助像我这样的初学者开始解决这个问题吗?再次感谢您的回答。
  • 如果您是多线程的初学者,请暂时远离无锁算法。它们比您需要的要先进得多,而且非常容易错误地实施。
  • 简单地删除 volatile 不是答案。我的建议是用真正的同步机制替换 volatile

标签: c++ multithreading winapi


【解决方案1】:

您不需要无锁同步。只需启动线程并在完成时让它们发出信号对象。如果您不想阻塞 UI 线程,请在同步句柄上执行非阻塞调用

WaitForMultipleObjects(n,lpHandles, 0, 0);

如果对象没有发出信号,最后一个 0 表示返回而不是等待

【讨论】:

  • 哇,答案很快!如果不是很多,您能否提供一些简单示例的链接?谢谢,感谢您的闪电般快速的答复!
  • 每次按下按钮,都会产生一个新线程。我可以使用向量来存储线程句柄。至于WaitForMultipleObjects,我不应该用WaitForMultipleObjects(n,lpHandles,TRUE,0)等待所有这些吗? ?当我销毁对话框时,可能需要像这样等待,对吧?
  • 轮询 0 等待答案,因此很糟糕。
  • @MartinJames 你能详细说明一下吗?
  • @pm100,在仔细考虑了所有提交的答案和 cmets 之后,我决定暂时避开多线程,因为我缺乏经验。我觉得我进入这种方式太乐观了,决定先看几本书,做几个程序,然后再尝试这样的事情。因此,我通知您我将删除此问题,因此我为您提供删除答案的选项。谢谢你的尝试,但在这一点上我太缺乏经验来实现它。问候。
【解决方案2】:

你可以这样处理:

  1. 创建一个包含所有工作线程的循环。这是一个真正的循环
  2. 在主线程(或对话框的构造函数)中启动此线程
  3. 循环将负责启动一些线程并等待它

这是我制作的一个示例:它由 main 和 C++ 11 制成,但可以转置(如果您需要更多信息或不同意,请咨询我)。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cstdlib>
#include <iostream>
#include <ctime>
using namespace std;

std::mutex m;
std::condition_variable cv;

atomic<bool> WorkerAvailable=true;
atomic<int> RandomVariable;

void WorkersLoop();
void worker_thread();

//this gathers the threads
void WorkersLoop()
{
    std::srand(std::time(0)); 
    int random_variable;
    //infinite loop for handling all the worker threads
    while (true)
    {
        random_variable= std::rand();
        //this actually simulates the user pressing a button
        if (random_variable%5==0)
        {
            // wait for the worker
            {
                std::unique_lock<std::mutex> lk(m);
                cv.wait(lk, []{return WorkerAvailable==true;});
            }
            std::lock_guard<mutex> lk(cout_mutex);
            {
                cout<<"thread started, random variable= "<<random_variable<<"\n";
            }
                    //this launches the actual work to be done
            std::thread abc(&worker_thread);
            // wait for the worker
            {
                std::unique_lock<std::mutex> lk(m);
                cv.wait(lk, []{return WorkerAvailable==true;});
            }
            abc.join();
        }
    }
}

//this does the work you want
void worker_thread()
{
    WorkerAvailable=false;

    std::this_thread::sleep_for(std::chrono::seconds(1));

    cout<<"thread  "<< this_thread::get_id()<<"finished"<<std::endl;

    WorkerAvailable=true;
    cv.notify_one();
}


int main()
{
        //launching the loop responsible for doing the threads
    thread Loop(WorkersLoop);
    Loop.join();
    return 0;
}

希望对你有帮助,如果不能回答你的问题,请告诉我我会更改或删除这篇文章

【讨论】:

  • 它可能对我有帮助,但我需要仔细研究这段代码,因为它是 C++11。这可能需要一些时间,希望你不介意。我感谢您的详细回答,我会按照您的要求做,我会尽快通知您您的回答是否有用。再次,非常感谢!
  • 只有一件事:当我使用随机变量并检查它的模 5 时,它在这里模拟用户输入(用户何时输入内容)。我稍后会添加到代码中
  • 好的,我明白了。谢谢。
  • 在仔细考虑了所有提交的答案和 cmets 之后,我决定暂时避开多线程,因为我缺乏经验。我觉得我进入这种方式太乐观了,决定先看几本书,做几个程序,然后再尝试这样的事情。因此,我通知您我将删除此问题,因此我为您提供删除答案的选项。感谢您的尝试,但在这一点上我太缺乏经验来实施它。问候。
猜你喜欢
  • 1970-01-01
  • 2017-08-13
  • 2018-04-06
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 2013-07-20
  • 2011-10-21
相关资源
最近更新 更多