C++ 并发专题 - 不变式与多线程

一:概述

        在C++中,“不变式”通常是指程序中某些必须始终成立的条件或者规则,破坏不变式(breaking Invariant Solved)的问题通常出现在并发编程中,当多个线程同时操作共享数据时,如果没有正确同步,可能导致数据不一致,进而违反了不变式。 

        在多线程程序中,不变式是指那些在整个程序运行过程中始终需要保持为真的条件或属性。比如:

        1. 某个变量的值不能超过特定范围。

        2. 一个计数器的值应该是非负数。

        3. 某个状态应该在任何时候都保持一致。 

二:例子(账户总余额不变)

#include <atomic>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>

struct Account
{
	int balance{ 100 };
};

std::mutex mutAccount;

void transferMoney(int amount, Account& from, Account& to)
{
	using namespace std::chrono_literals;
	{
		std::lock_guard<std::mutex> lock(mutAccount);
		if (from.balance >= amount)
		{
			from.balance -= amount;
			std::this_thread::sleep_for(10ns);
			to.balance += amount;
		}
	}
}

void printSum(Account& a1, Account& a2)
{
	std::lock_guard<std::mutex> lock(mutAccount);
	std::cout << (a1.balance + a2.balance) << '\n';
}

int main()
{
	std::cout << '\n';

	Account acc1; 
	Account acc2;

	std::cout << "initial sum: "; 
	printSum(acc1, acc2);

	std::thread thr1(transferMoney, 5, std::ref(acc1), std::ref(acc2));
	std::thread thr2(transferMoney, 13, std::ref(acc2), std::ref(acc1));

	std::cout << "Intermediate sum: ";
	std::thread thr3(printSum, std::ref(acc1), std::ref(acc2));

	thr1.join();
	thr2.join();
	thr3.join();

	std::cout << "  acc1.balance: " << acc1.balance << '\n';
	std::cout << "  acc2.balance: " << acc2.balance << '\n';

	std::cout << "Final sum: "; 
	printSum(acc1, acc2);
	std::cout << '\n';

}

三: 破坏不变式的情况和示例

        破坏不变式通常发生在多线程程序中,尤其是当多个线程访问共享数据时,如果没有正确同步,可能会导致以下问题:

        1. 竞态条件:多个线程同时读写共享数据而没有适当的同步机制,导致数据不一致。

        2. 死锁:多个线程在等待对方释放资源时,导致程序无法继续运行。

        3. 不一致的状态:即使没有竞态条件,线程在更新数据时的时序问题可能导致不变式被破坏。

        下面介绍一个例子,多线程程序中,如果没有同步机制,共享变量会导致不一致的情况:

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

std::mutex mtx;
int shared_value = 0;

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

void decrement() {
    for (int i = 0; i < 100000; ++i) {
        shared_value--;
    }
}

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

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

    // 期望 shared_value 为 0,但由于缺少同步,可能不为 0
    std::cout << "Shared value: " << shared_value << std::endl;

    return 0;
}

四:解决不变式破坏问题的方法: 

        解决不变式被破坏问题的方法通常涉及到正确的线程同步,可以使用 std::mutex 来确保同一时刻只有一个个线程访问共享资源,从而避免竞态条件。以下是解决上述问题的示例代码:

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

std::mutex mtx;
int shared_value = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 使用锁来保护共享资源
        shared_value++;
    }
}

void decrement() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 使用锁来保护共享资源
        shared_value--;
    }
}

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

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

    // 现在,shared_value 应该是 0
    std::cout << "Shared value: " << shared_value << std::endl;

    return 0;
}

猜你喜欢

转载自blog.csdn.net/zg260/article/details/143582315