这个函数是线程安全的吗?

时间:2010-05-05 08:21:27

标签: c++ multithreading mfc

我正在学习多线程并且为了理解我使用多线程来完成一个小函数...它运行正常。但我只是想知道该线程是否可以安全使用,我是否遵循了正确的规则

void CThreadingEx4Dlg::OnBnClickedOk()
{
    //in thread1 100 elements are copied to myShiftArray(which is a CStringArray)
    thread1 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction1,this);
    WaitForSingleObject(thread1->m_hThread,INFINITE);
    //thread2 waits for thread1 to finish because thread2 is going to make use of myShiftArray(in which thread1 processes it first)
    thread2 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction2,this);
    thread3 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction3,this);

}

UINT MyThreadFunction1(LPARAM lparam)
{
    CThreadingEx4Dlg* pthis = (CThreadingEx4Dlg*)lparam;
    pthis->MyFunction(0,100);
    return 0;
}
UINT MyThreadFunction2(LPARAM lparam)
{
    CThreadingEx4Dlg* pthis = (CThreadingEx4Dlg*)lparam;
    pthis->MyCommonFunction(0,20);
    return 0;
}

UINT MyThreadFunction3(LPARAM lparam)
{
    CThreadingEx4Dlg* pthis = (CThreadingEx4Dlg*)lparam;
    WaitForSingleObject(pthis->thread3->m_hThread,INFINITE);
    //here thread3 waits for thread 2 to finish so that thread can continue
    pthis->MyCommonFunction(21,40);
    return 0;
}
void CThreadingEx4Dlg::MyFunction(int minCount,int maxCount)
{

    for(int i=minCount;i<maxCount;i++)
    {
        //assume myArray is a CStringArray and it has 100 elemnts added to it.
        //myShiftArray is a CStringArray -public to the class
        CString temp;
        temp = myArray.GetAt(i);
        myShiftArray.Add(temp);
    }

}

void CThreadingEx4Dlg::MyCommonFunction(int min,int max)
{
    for(int i = min;i < max;i++)
    {
        CSingleLock myLock(&myCS,TRUE);
        CString temp;
        temp = myShiftArray.GetAt(i);
        //threadArray is CStringArray-public to the class
        threadArray.Add(temp);
    }
    myEvent.PulseEvent();

}

3 个答案:

答案 0 :(得分:2)

您打算哪种功能是“线程安全的”?

我认为该术语应该适用于您的CommonFunction。这是一个你打算被称为几个(在第一种情况下是两个)线程的函数。

我认为您的代码的规则如下:

Thread 2 do some work

meanwhile Thread 3 wait until Thread 2 finishes then you do some work

实际上你的代码有

WaitForSingleObject(pthis->thread3->m_hThread,INFINITE);

也许等待错误的帖子?

但回到线程安全。安全监管在哪里?它在你的线程的控制逻辑中。假设你有很多线程,你会如何扩展你所写的内容?你有很多这样的逻辑:

if thread a has finished and thread b has finished ...

真的很难做到正确并保持。相反,你需要使CommonFunction真正的线程安全,即它需要容忍同时由多个线程调用。

在这种情况下,您可以通过在代码的关键部分放置某种互斥量来实现这一点,在这种情况下可能是整个函数 - 不清楚您是否打算保留复制的项目或是否请注意,如果值是交错的。

在后一种情况下,唯一的问题是访问myArray和myShiftArray是否是线程安全的集合

    temp = myArray.GetAt(i);
    myShiftArray.Add(temp);

所有其他变量都是本地的,在当前线程拥有的堆栈上 - 因此您只需查阅这些集​​合的文档,以确定它们是否可以安全地由单独的线程调用。

答案 1 :(得分:0)

看起来这会起作用,因为线程不会同时进行任何工作。如果更改代码以使线程同时执行,则需要在访问线程之间共享的数据成员的代码周围放置互斥锁(在MFC中,您可以使用CCriticalSection)。

答案 2 :(得分:0)

正如我之前所指出的那样,你所做的事情完全没有意义,你可以不使用线程来解除线程,然后等待线程完成,然后再做任何事情。

您提供有关您的CEvent的宝贵信息,但您的WaitForSingleObjects正在等待线程进入信号状态(即让他们退出)。

由于MyCommonFunction是发生实际潜在线程不安全事件的地方,因此您对该区域进行了正确的临界分区,但是,线程2和线程3不会同时运行。从MyThreadFunction3中删除WaitForSingleObject,然后你将以线程安全的方式同时运行,这要归功于关键部分。

那说它仍然有点无意义,因为两个线程将花费大部分时间等待关键部分自由。一般来说,你想要构造线程,这样他们就可以获得很少的关键部分,然后,当他们遇到一个关键部分时,只打了很短的时间(即不是函数处理时间的绝大部分)

修改

关键部分 的工作原理是说我要抓住这个关键部分还有其他任何需要等待的部分。这意味着线程1进入关键部分并开始执行它需要做的事情。然后线程2出现并说“我想使用临界区”。内核告诉它“线程1正在使用您必须等待轮到你的关键部分”。线程3出现并被告知相同的事情。线程2和3现在处于等待状态,等待该关键部分释放。当线程1以关键部分结束时,线程2和3都会竞争以查看谁先获得关键部分,当获得它时,另一个必须继续等待。

现在在上面的示例中,将会有很多等待关键部分,可能是线程1可以在关键部分和线程2等待并且在线程2被赋予进入关键部分的机会之前线程1具有绕回并重新进入。这意味着线程1可能会在线程2有机会进入临界区之前完成所有工作。因此,将临界区中完成的工作量与循环/函数的其余部分相比尽可能低,将有助于线程同时运行。在你的例子中,一个线程总是等待另一个线程,因此只是串行执行可能实际上更快,因为你没有内核线程开销。

即,避免CriticalSections越多,线程等待的时间就越少。但是,它们是必要的,因为您需要确保2个线程不会同时尝试对同一个对象进行操作。某些内置对象"atomic"可以为您提供帮助,但对于非原子操作,关键部分是必须的。

事件 是一种不同类型的同步对象。基本上,事件是一个可以是两种状态之一的对象。发信号或未发信号。如果WaitForSingleObject处于“未发出信号”的事件,那么该线程将进入休眠状态,直到它进入信号状态。

当你有一个必须等​​待另一个线程完成某个东西的线程时,这会很有用。通常,尽管您希望尽可能避免使用此类同步对象,因为它会破坏代码的并行性。

当我有一个工作线程等待它需要做某事时,我个人使用它们。线程大多数时间处于等待状态,然后当需要进行某些后台处理时,我会发出事件信号。然后线程跳入生命并执行它需要做的事情,然后循环回来并重新进入等待状态。您还可以将变量标记为指示对象需要退出。这样,您可以将exit变量设置为true,然后发出等待线程的信号。等待的线程醒来并说“我应该退出”,然后退出。但是要警告你“可能”需要一个memory barrier来确保在事件被唤醒之前设置退出变量,否则编译器可能会重新排序操作。这可能最终让你的线程醒来发现退出变量没有设置做它的事情,然后又回到睡眠状态。但是,最初发送信号的线程现在假定线程实际上没有退出。

谁说多线程很容易? ;)