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

Posted

技术标签:

【中文标题】这个函数是线程安全的吗?【英文标题】:Is this function thread-safe? 【发布时间】:2010-05-05 08:21:27 【问题描述】:

我正在学习多线程,为了理解起见,我使用多线程编写了一个小函数......它工作正常。但我只想知道该线程是否可以安全使用,我是否遵循了正确的规则.

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();


【问题讨论】:

【参考方案1】:

您打算将哪个函数设为“线程安全”?

我认为该术语应该适用于您的 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);

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

【讨论】:

感谢您的回复,?我不明白“可能等待错误的线程”的意思..它如何等待错误的线程..我已经给了线程3的句柄..那么如何会不会出错..你能解释一下吗? 也许我误解了你的代码(我做 Java 不是 .Net)我不确定代码 WaitForSingleObject(pthis->thread3->m_hThread 是否应该通过正在等待的线程:thread3,或者正在等待完成的线程:thread2.【参考方案2】:

正如我之前指出的那样,你所做的事情完全没有意义,你最好不要使用线程,因为你会触发一个线程,然后等待线程完成,然后再做进一步的事情。

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

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

这仍然有点毫无意义,因为两个线程都将花费大部分时间等待关键部分空闲。一般来说,您希望构建线程,以便它们几乎不需要触及关键部分,然后,当它们触及关键部分时,只在很短的时间内击中它(即不是函数的绝大多数处理时间) .

编辑

关键部分的工作方式是说我持有这个关键部分,其他任何想要它的东西都必须等待。这意味着线程 1 进入临界区并开始做它需要做的事情。然后线程 2 出现并说“我想使用临界区”。内核告诉它“线程 1 正在使用你必须等待轮到你的关键部分”。线程 3 出现并被告知同样的事情。线程 2 和 3 现在处于等待状态,等待该临界区空闲。当线程 1 完成临界区时,线程 2 和 3 都竞相看谁先持有临界区,当一个获得临界区时,另一个必须继续等待。

现在在上面的示例中,等待临界区的时间非常多,线程 1 可能处于临界区而线程 2 正在等待,在线程 2 有机会进入临界区之前,线程 1 有可能循环回来并重新进入它。这意味着线程 1 可能在线程 2 有机会进入临界区之前完成所有工作。因此,与循环/函数的其余部分相比,保持关键部分完成的工作量尽可能低将有助于线程同时运行。在您的示例中,一个线程将始终等待另一个线程,因此仅串行执行它实际上可能会更快,因为您没有内核线程开销。

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

事件是一种不同的同步对象。基本上,事件是可以是两种状态之一的对象。有信号或无信号。如果您在“未发出信号”事件上使用 WaitForSingleObject,则线程将进入睡眠状态,直到它进入发出信号状态。

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

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

谁说多线程很容易啊? ;)

【讨论】:

我清楚地理解了 CriticalSection 和 CEvent Synchronization 的 b/w 区别......但我很困惑如何以及何时使用它......可以通过使用 criticalsection 和 CEvent 给我一个简单的例子(到信号)..提前感谢 只是为了了解......我们在什么样的情况下使用它? 好吧,我已经更新了我的答案……我可以继续说下去。这是一个相当深入的领域;) 非常感谢...我必须编写越来越多的示例,并且应该添加一些曲折(如 MKnight Shamalon 电影)来玩多线程...【参考方案3】:

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

【讨论】:

感谢您的回复,我确实使用了 CCriticalSection ......请检查“MyCommonFunction”......就像你说的线程不会同时工作......我知道。我写了这段代码了解CEvent和WaitForSingleObject的概念

以上是关于这个函数是线程安全的吗?的主要内容,如果未能解决你的问题,请参考以下文章

C/C++线程安全型队列的实现

JNI 函数是线程安全的吗?

静态函数范围对象的构造是线程安全的吗?

+= 运算符在 Python 中是线程安全的吗?

原子增加和比较是线程安全的吗

使用atomic一定是线程安全的吗