我需要为读取操作使用互斥锁吗?

7 浏览
0 Comments

我需要为读取操作使用互斥锁吗?

我有一个包含状态(一个简单的枚举)的类,可以从两个线程访问。为了改变状态,我使用了一个互斥锁(boost::mutex)。在这种情况下,检查状态(例如,比较state_ == ESTABLISHED)是安全的吗?或者我在这种情况下也需要使用互斥锁?换句话说,当我只想读取一个可能被另一个线程并发写入的变量时,我是否需要互斥锁?

0
0 Comments

在多线程编程中,当一个线程正在写入一个变量时,另一个线程可能正在读取该变量。如果读操作和写操作不是原子操作,尤其是在多处理器系统中,就可能读取到一个未定义的值。

当一个写线程执行(fetch->write->store)操作时,在这个过程中,我看不出一个读线程会在这个过程的中间阶段读取到一个未定义的值,可能会读取到之前的值或之后的值,但绝不会是未定义的值。

需要考虑的是,在一条指令中读取的枚举值。

然而,随着CPU和内存架构的不断复杂化,增加了多级缓存和更高的延迟等,这里的“未定义”可能并不是恰当的词。答案很简单:对于数据的无锁共享,说“不”!即使是专家在这个领域也会犯很多错误。

因此,为了避免读线程读取到未定义的值,需要使用互斥锁(mutex)来保护读操作。使用互斥锁可以确保同一时间只能有一个线程访问共享变量,从而避免并发读写导致的问题。

以下是一个使用互斥锁的示例代码:

#include

#include

#include

std::mutex mtx;

int sharedVariable = 0;

void readThread()

{

std::lock_guard lock(mtx); // 加锁

std::cout << "Read value: " << sharedVariable << std::endl;

}

void writeThread()

{

std::lock_guard lock(mtx); // 加锁

sharedVariable = 42;

}

int main()

{

std::thread t1(readThread);

std::thread t2(writeThread);

t1.join();

t2.join();

return 0;

}

在上述代码中,互斥锁`mtx`被用来保护共享变量`sharedVariable`的读写操作。在读线程中,通过调用`std::lock_guard`的构造函数来获取互斥锁,并在作用域结束时自动释放锁。写线程同样也是如此。

通过使用互斥锁,可以确保读线程和写线程之间的互斥访问,从而避免读线程读取到未定义的值。

0
0 Comments

在多线程程序中,当有两个线程交换信息时,我们通常需要使用互斥锁(mutex)以及条件变量(conditional wait)来确保线程之间的同步。在下面的代码示例中,比较(state_ == ESTABLISHED)表明线程#2正在等待线程#1来初始化一个连接/状态。如果没有互斥锁或条件变量,线程#2就必须持续地轮询状态。

然而,使用轮询会导致性能下降,要么是因为消耗大量的CPU资源,要么是因为轮询间隔引入了延迟。为了提高性能和响应性,我们可以使用条件变量。

条件变量是一种线程间的同步机制,它允许一个线程等待另一个线程满足特定的条件。在这种情况下,如果一个线程需要根据其他线程的状态变化来响应,那么条件变量是更合适的选择。

那么,如果我们打算使用忙等待循环(busy wait loop),我们是否仍然需要互斥锁呢?答案是肯定的。尽管忙等待循环可以在循环期间执行其他操作,但为了保护共享资源的一致性,我们仍然需要使用互斥锁。

当多个线程之间存在信息交换和状态变化时,我们通常需要使用互斥锁和条件变量来确保线程的同步和共享资源的一致性。互斥锁用于保护共享资源,条件变量用于线程之间的等待和通知。通过合理使用这些同步机制,我们可以提高多线程程序的性能和可靠性。

0
0 Comments

读取一个整数是一个原子操作,所以在大多数现代CPU上,即使没有互斥锁,也会读取到一致的值。但是,如果涉及到访问多个变量,没有互斥锁或其他形式的同步,编译器和CPU可以自由地重新排序读取和写入操作,这在一般情况下是不安全的。

假设写入线程更新了一些数据,并设置了一个整数标志来通知其他线程数据已经可用,这可能会重新排序,使得标志在更新数据之前被设置。除非使用互斥锁或其他形式的内存屏障,否则会出现这种情况。

所以,如果你想要正确的行为,不一定需要互斥锁,如果另一个线程在你读取变量时写入它,也没有问题。除非你在一个非常不寻常的CPU上工作,否则它将是原子的。但是你确实需要一种内存屏障来防止编译器或CPU的重新排序。

除非指定为volatile,否则读取(或写入)可能永远不会执行。不早不晚都不够好。但是即使使用volatile,CPU或编译器也可能重新排序写入操作,使其变得无意义。正确的解决方案是使用内存屏障,而volatile只是一种不必要的优化。

对于只有一个标志的情况,不需要互斥锁。但是,如果检查标志来确定应用程序的下一步,要么需要在线程之间共享其他数据,要么标志检查是无意义的,可以始终执行下一步。假设(a)是情景,只因为标志变量被设置, "其他数据" 可能还没有准备好。假设是因为标志变量导致的运行时调试崩溃的可能性很大。

0