📜  在C ++中实现竞争条件(1)

📅  最后修改于: 2023-12-03 14:51:14.697000             🧑  作者: Mango

在C++中实现竞争条件

在多线程环境下,一个共享变量的值同时被两个或多个线程访问、读取和修改,就会出现竞争条件(Race condition)。竞争条件可能导致程序的不可预期行为,甚至崩溃。

C++11引入了一组旨在解决这种问题的工具,包括互斥锁(mutex)、条件变量(condition variable)、原子操作(atomic operation)等。下面我们通过示例来介绍如何在C++中实现竞争条件。

互斥锁

互斥锁是一种保护共享资源的最基本的机制,一个线程试图获取互斥锁时,如果互斥锁已经被其他线程获取,则该线程会阻塞等待,直到互斥锁被释放。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex myMutex;
int mySharedInteger = 0;

void increment()
{
    myMutex.lock();
    mySharedInteger ++;
    myMutex.unlock();
}

int main()
{
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();

    std::cout << "mySharedInteger = " << mySharedInteger << std::endl;
    
    return 0;
}

上述代码中,我们定义了一个全局的互斥锁 myMutex 和一个全局的共享变量 mySharedInteger。然后我们启动了两个线程 t1 和 t2,每个线程都会执行 increment 函数,该函数首先加锁互斥锁,然后对共享变量进行加 1 操作,最后释放互斥锁。这样,我们就保证了在同一时刻只有一个线程可以修改共享变量,消除了竞争条件。

条件变量

条件变量用于线程之间的通信,通过条件变量,一个线程可以等待另一个线程满足某个条件才继续执行。当线程发现自己不能继续执行时,它可以等待条件变量,而不是忙等待,这样可以避免竞争条件和效率问题。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex myMutex;
std::condition_variable myCondVar;
bool mySharedFlag = false;

void dataWaitingThread()
{
    std::cout << "Data waiting thread is waiting..." << std::endl;
    std::unique_lock<std::mutex> lock(myMutex);
    myCondVar.wait(lock, []() {return mySharedFlag;});
    std::cout << "Data waiting thread got the data." << std::endl;
}

void dataReadingThread()
{
    std::cout << "Data reading thread is reading data..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));
    mySharedFlag = true;
    myCondVar.notify_one();
    std::cout << "Data reading thread finished reading data." << std::endl;
}

int main()
{
    std::thread t1(dataWaitingThread);
    std::thread t2(dataReadingThread);

    t1.join();
    t2.join();

    return 0;
}

上述代码中,我们定义了一个互斥锁 myMutex、一个条件变量 myCondVar 和一个共享 bool 变量 mySharedFlag。我们启动了两个线程,其中一个线程 dataWaitingThread 会等待条件变量,判断共享 bool 变量是否为 true,如果不是,则阻塞等待条件变量。另一个线程 dataReadingThread 会执行某些操作,最后将共享 bool 变量修改为 true,并且通知一个等待线程条件变量已经满足。这样 dataWaitingThread 就可以继续执行了。

原子操作

原子操作是指不能被中断的操作,它们是通过硬件支持实现的,能够保证在多线程并发访问时的正确性。C++11提供了一些原子操作,如 std::atomic<int> 类型,可以保证原子读取和写入操作。

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> mySharedAtomicInteger(0);

void increment()
{
    for (int i = 0; i < 100000; ++i)
    {
        mySharedAtomicInteger ++;
    }
}

int main()
{
    std::thread t1(increment);
    std::thread t2(increment);
    
    t1.join();
    t2.join();

    std::cout << "mySharedAtomicInteger = " << mySharedAtomicInteger << std::endl;
    
    return 0;
}

上述代码中,我们定义了一个全局的原子变量 mySharedAtomicInteger,我们启动了两个线程 t1 和 t2,每个线程都会执行 increment 函数,该函数对原子变量进行加 1 操作。由于原子操作是不可中断的,因此我们不需要使用互斥锁或其他同步机制来保护共享变量,也能够消除竞争条件。

结论

在多线程环境下,竞争条件是一个常见的问题。C++11提供了一些工具来解决这个问题,包括互斥锁、条件变量、原子操作等,我们需要根据不同的情况选择合适的工具来实现线程安全的并发操作。