繁体   English   中英

线程安全编程

[英]Thread safe programming

我一直听说线程安全。 究竟是什么以及如何以及在何处学习编写线程安全代码?

另外,假设我有2个线程,一个写入结构,另一个从中读取。 这有什么危险吗? 有什么我应该寻找的吗? 我不认为这是一个问题。 两个线程都不会(很不能)在同一时间访问结构。

此外,有人可以告诉我如何在这个例子中: https//stackoverflow.com/a/5125493/1248779我们在并发问题上做得更好。 我不明白。

这是一个非常深刻的话题。 心脏线程通常是通过同时使用多个核心来使事情变得更快; 或者当你没有一个很好的方法将操作与'主'线程交错时,在后台进行长时间的操作。 后者在UI编程中非常常见。

你的场景是经典的麻烦点之一,也是最早遇到的人之一。 拥有一个成员真正独立的结构是很少见的。 想要修改结构中的多个值以保持一致性是很常见的。 在没有任何预防措施的情况下,很有可能修改第一个值,然后让另一个线程读取结构并在写入第二个值之前对其进行操作。

简单的例子是2d图形的'点'结构。 您想将点从[2,2]移到[5,6]。 如果你有一个不同的线程画一条线到那一点你可能很容易画到[5,2]。

这真的是冰山一角。 有很多好书,但学习这个空间通常是这样的:

  1. 哦,我只是从那个处于不一致状态的东西中读出来的。
  2. 哦,我只是从2个线程修改了这个东西,现在它是垃圾。
  3. 好极了! 我了解了锁
  4. 哇,我有很多锁,当我有很多锁定嵌套代码时,一切似乎都会挂起。
  5. 人力资源管理。 我需要在飞行中停止这种锁定,我似乎错过了很多地方; 所以我应该将它们封装在数据结构中。
  6. 那个数据结构很棒,但是现在我似乎一直在锁定,而且我的代码和单个线程一样慢。
  7. 条件变量很奇怪
  8. 这很快,因为我对如何锁定东西很聪明。 人力资源管理。 有时数据会破坏。
  9. 哇.... InterlockedWhatDidYouSay?
  10. 嘿,看起来没锁,我做这个叫旋转锁的东西。
  11. 条件变量。 嗯...我明白了。
  12. 你知道吗,我只是开始考虑如何以完全独立的方式操作这些东西,管理我的操作,以及尽可能少的跨线程依赖...

显然,这不仅仅是条件变量。 但是有很多问题可以通过线程解决,并且可能几乎有很多方法可以解决,甚至更多的方法可以解决问题。

线程安全性是“并发编程”一般标题下更大问题的一个方面。 我建议阅读那个主题。

您假设两个线程无法同时访问该结构并不好。 第一:今天我们有多核机器,因此两个线程可以在同一时间运行。 第二:即使在单核心机器上,给予任何其他线程的时间片也是不可预测的。 您必须在“其他”线程可能正在处理的任意时间内预测该蚂蚁。 请参阅下面的“机会之窗”示例。

线程安全的概念正好回答了“这在任何方面都是危险的”这个问题。 关键问题是,在一个线程中运行的代码是否有可能获得某些数据的不一致视图,这种不一致的情况发生,因为在运行另一个线程时正在更改数据。

在您的示例中,一个线程正在读取结构,同时另一个正在编写。 假设有两个相关字段:

  { foreground: red; background: black }

而作者正在改变这些

   foreground = black;
            <=== window of opportunity
   background = red;

如果读者只是在那个机会窗口读取值,那么它就会看到一个“无意义”的组合

  { foreground: black; background: black }

这种模式的本质是,在短时间内,当我们进行更改时,系统变得不一致,读者不应该使用这些值。 一旦我们完成更改,再次阅读就变得安全了。

因此,我们使用Stefan提到的CriticalSection API来防止线程看到不一致的状态。

究竟是什么?

简而言之,可以在并发上下文中执行而没有与并发相关的错误的程序。

如果ThreadA和ThreadB无错误地读取和/或写入数据并使用正确的同步,则程序可能是线程安全的。 这是一个设计选择 - 使对象线程安全可以通过多种方式实现,更复杂的类型可以使用这些技术的组合进行线程安全。

以及如何以及在何处学习编写线程安全代码?

boost / libs / thread /可能是一个很好的介绍。 这个话题非常复杂。

C ++ 11标准库提供了锁,原子和线程的实现 - 任何编写良好的程序都可以很好地阅读。 标准库在boost实现后建模。

另外,假设我有2个线程,一个写入结构,另一个从中读取。 这有什么危险吗? 有什么我应该寻找的吗?

是的,它可能是危险的和/或可能产生不正确的结果。 想象一下,线程可能在任何时候耗尽它的时间,然后另一个线程可以读取或修改该结构 - 如果你没有保护它,它可能正处于更新的中间。 常见的解决方案是锁,可用于防止另一个线程在读/写期间访问共享资源。

在WIN32平台上编写多线程C ++程序时,需要保护某些共享对象,以便只有一个线程可以在任何给定时间从不同的线程访问它们。 您可以使用5个系统功能来实现此目的。 它们是InitializeCriticalSection,EnterCriticalSection,TryEnterCriticalSection,LeaveCriticalSection和DeleteCriticalSection。

也许这个链接可以帮助: 如何使应用程序线程安全?

http://www.codeproject.com/Articles/1779/Making-your-C-code-thread-safe

线程安全是一个简单的概念:在一个线程上执行操作A而另一个线程正在执行操作B是“安全的”,操作B可能与操作A相同或不同。这可以扩展到覆盖许多线程。 在这种情况下,“安全”是指:

  • 没有未定义的行为
  • 线程可以保证数据结构的所有不变量都被观察到

实际操作A和B很重要。 如果两个线程都读取一个普通的int变量,那么这很好。 但是,如果任何线程可能写入该变量,并且没有同步来确保读取和写入不能一起发生,那么您有一个数据争用,这是未定义的行为,这不是线程安全的。

这同样适用于您所询问的场景:除非您采取了特殊的预防措施,否则在另一个线程写入的同时从一个结构读取一个线程是不安全的 如果可以保证线程不能同时访问数据结构,通过某种形式的同步,如互斥,临界区,信号量或事件,那么就没有问题。

您可以使用互斥锁和关键部分之类的东西来防止对某些数据的并发访问,这样写入线程就是在写入时访问数据的唯一线程,并且读取线程是在读取时访问数据的唯一线程,从而提供我刚才提到的保证。 因此,这避免了上面提到的未定义的行为。

但是,您仍然需要确保您的代码在更广泛的上下文中是安全的:如果您需要修改多个变量,那么您需要在整个操作中保持对互斥锁的锁定,而不是对每个单独的访问进行锁定,否则您可能发现其他线程可能无法观察到您的数据结构的不变量。

对于某些操作而言,数据结构也可能是线程安全的而不是其他操作。 例如,如果一个线程正在推送队列中的项目而另一个正在从队列中弹出项目,则单生产者单用户队列将是正常的,但如果两个线程正在推送项目,或者两个线程正在弹出项目,则会中断。

在您引用的示例中,重点是全局变量在所有线程之间隐式共享,因此如果任何线程可以修改它们,则必须通过某种形式的同步(例如互斥锁)保护所有访问。 另一方面,如果每个线程都有一个单独的数据副本,那么该线程可以修改其副本而不必担心来自任何其他线程的并发访问,并且不需要同步。 当然,如果两个或多个线程要对相同的数据进行操作,则始终需要同步。

我的书“ C ++ Concurrency in Action”涵盖了线程安全,如何设计线程安全数据结构以及用于此目的的C ++同步原语,例如std::mutex

线程安全是指保护某个代码块不被多个线程访问。 意味着操纵的数据始终保持一致状态。

一个常见的例子是生产者消费者问题,其中一个线程从数据结构读取而另一个线程写入相同的数据结构: 详细解释

回答问题的第二部分:想象两个线程都访问std::vector<int> data

//first thread
if (data.size() > 0)
{
   std::cout << data[0]; //fails if data.size() == 0
}

//second thread
if (rand() % 5 == 0)
{
   data.clear();
}
else
{
   data.push_back(1);
}

并行运行这些线程,程序将崩溃,因为std::cout << data[0]; 可能会在data.clear();之后直接执行data.clear();

您需要知道在线程代码的任何位置,线程可能会被中断,例如在检查(data.size() > 0) ,另一个线程可能变为活动状态。 虽然第一个线程在单线程应用程序中看起来是正确的,但它不在多线程程序中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM