此外,如果我有两个线程,一个写入一个结构体,另一个从中读取,这会有危险吗?我需要留意哪些方面呢?我认为这不是问题。两个线程没有可能同时访问结构体。
还有,请问在这个例子https://dev59.com/Km435IYBdhLWcg3w7k6b#5125493中我们如何更好地处理并发问题?我不太理解。
线程安全是“并发编程”这个总体问题集合中的一个方面。建议您对该主题进行更多的阅读。
您认为两个线程不能同时访问结构体的假设是不正确的。首先:今天我们有多核机器,因此两个线程可以完全同时运行。其次:即使在单核机器上,分配给其他任何线程的时间片是不可预测的。您必须预期在任意时间,“其他”线程可能正在处理。请参见下面我的“机会窗口”示例。
线程安全的概念正是为了回答“这样做是否存在任何危险”的问题。关键问题是代码在一个线程中运行时是否可能得到一些数据的不一致视图,这种不一致性是由于另一个线程在更改数据时它正在运行而发生的。
在您的示例中,一个线程正在读取一个结构体,同时另一个线程正在写入。假设有两个相关字段:
{ 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的实现而建立的。
假设我有两个线程,一个线程写入一个结构,另一个线程从中读取。这样做有什么危险吗?我应该注意什么吗?
是的,这可能是危险的和/或可能产生不正确的结果。想象一下一个线程可能在任何时候运行完毕,然后另一个线程就可以读取或修改该结构--如果您没有保护它,它可能处于更新过程中。一种常见的解决方法是锁定,可以用于防止另一个线程在读取/写入期间访问共享资源。
int myFunc(struct myState *state)
如果@a state可能被另一个线程写入,则不是线程安全的。如果@a state是线程本地数据,则它将是线程安全的。换句话说,使此程序线程安全的不是封装,而是@a state指向的对象是线程本地而不是全局的。还要注意,Theo确实指定@a state是线程本地的。 - justinhttp://www.codeproject.com/Articles/1779/Making-your-C-code-thread-safe
您可以使用诸如互斥锁和临界区等工具来防止对某些数据的并发访问,以便在写入线程正在写入数据时,只有该线程访问数据,而在读取线程正在读取数据时,只有该线程访问数据,从而提供了我刚才提到的保证。因此,这避免了上述提到的未定义行为。
但是,您仍然需要确保您的代码在更广泛的上下文中是安全的:如果您需要修改多个变量,则需要在整个操作期间持有互斥锁的锁定,而不是每个单独访问都需要锁定,否则您可能会发现其他线程无法观察到数据结构的不变量。
还有可能出现一种情况,即某个数据结构对于某些操作是线程安全的,但对于其他操作则不是。例如,单生产者单消费者队列将在一个线程将项目推送到队列中,另一个线程将项目从队列中弹出时正常工作,但如果两个线程都在推送项目或两个线程都在弹出项目,则会出现问题。
在你提到的例子中,关键在于全局变量隐式共享在所有线程之间,因此所有访问都必须受到某种形式的同步保护(如互斥锁),如果任何一个线程可以修改它们。另一方面,如果每个线程都有一个单独的数据副本,则该线程可以修改其副本而不必担心来自任何其他线程的并发访问,并且不需要同步。当然,如果两个或多个线程将操作相同的数据,则始终需要同步。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.size() > 0)
之后,另一个线程可能变得活跃。虽然第一个线程在单线程应用程序中看起来是正确的,但在多线程程序中却不是。
线程安全是指某个代码块受到保护,以防止多个线程访问。这意味着被操作的数据始终保持一致的状态。
一个常见的例子是生产者消费者问题,其中一个线程从数据结构中读取,而另一个线程向同一数据结构写入:详细解释