使用 RAII 管理并发对象的生存期
并发运行时使用异常处理来实现取消等功能。 因此,在调用运行时,或调用另一个调用运行时的库时,请编写异常安全的代码。
“资源获取即初始化”(RAII) 模式是在给定范围内安全管理并发对象生存期的一种方式。 在 RAII 模式下,数据结构在堆栈上分配。 该数据结构在创建资源时初始化或获取资源,并在销毁数据结构时销毁或释放该资源。 RAII 模式保证在封闭范围退出之前调用析构函数。 当函数包含多个 return 语句时,此模式非常有用。 此模式还可帮助你编写异常安全的代码。 当 throw 语句导致堆栈展开时,会调用 RAII 对象的析构函数;因此,始终可以正确删除或释放资源。
运行时定义了几种使用 RAII 模式的类,例如 concurrency::critical_section::scoped_lock 和 concurrency::reader_writer_lock::scoped_lock。 这些帮助程序类称为“作用域锁”。 在使用 concurrency::critical_section 或 concurrency::reader_writer_lock 对象时,这些类可提供诸多好处。 这些类的构造函数获取对所提供的 critical_section 或 reader_writer_lock 对象的访问权限;析构函数释放对该对象的访问权限。 由于有范围的锁在被销毁时会自动释放对其互相排斥对象的访问权限,因此你不需要手动解锁基础对象。
请考虑以下 account 类,它由外部库定义,因此无法修改。
// account.h
#pragma once
#include <exception>
#include <sstream>
// Represents a bank account.
class account
{
public:
explicit account(int initial_balance = 0)
: _balance(initial_balance)
{
}
// Retrieves the current balance.
int balance() const
{
return _balance;
}
// Deposits the specified amount into the account.
int deposit(int amount)
{
_balance += amount;
return _balance;
}
// Withdraws the specified amount from the account.
int withdraw(int amount)
{
if (_balance < 0)
{
std::stringstream ss;
ss << "negative balance: " << _balance << std::endl;
throw std::exception((ss.str().c_str()));
}
_balance -= amount;
return _balance;
}
private:
// The current balance.
int _balance;
};
以下示例对 account 对象并行执行多个事务。 该示例使用 critical_section 对象来同步对 account 对象的访问,因为 account 类不是并发安全的。 每个并行操作使用一个 critical_section::scoped_lock 对象来保证在操作成功或失败时解锁 critical_section 对象。 当帐户余额为负时,withdraw 操作会引发异常并失败。
// account-transactions.cpp
// compile with: /EHsc
#include "account.h"
#include <ppl.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create an account that has an initial balance of 1924.
account acc(1924);
// Synchronizes access to the account object because the account class is
// not concurrency-safe.
critical_section cs;
// Perform multiple transactions on the account in parallel.
try
{
parallel_invoke(
[&acc, &cs] {
critical_section::scoped_lock lock(cs);
wcout << L"Balance before deposit: " << acc.balance() << endl;
acc.deposit(1000);
wcout << L"Balance after deposit: " << acc.balance() << endl;
},
[&acc, &cs] {
critical_section::scoped_lock lock(cs);
wcout << L"Balance before withdrawal: " << acc.balance() << endl;
acc.withdraw(50);
wcout << L"Balance after withdrawal: " << acc.balance() << endl;
},
[&acc, &cs] {
critical_section::scoped_lock lock(cs);
wcout << L"Balance before withdrawal: " << acc.balance() << endl;
acc.withdraw(3000);
wcout << L"Balance after withdrawal: " << acc.balance() << endl;
}
);
}
catch (const exception& e)
{
wcout << L"Error details:" << endl << L"\t" << e.what() << endl;
}
}
此示例产生以下示例输出:
Balance before deposit: 1924
Balance after deposit: 2924
Balance before withdrawal: 2924
Balance after withdrawal: -76
Balance before withdrawal: -76
Error details:
negative balance: -76
不要在全局范围创建并发对象
在全局范围内创建并发对象时,会导致应用程序中出现死锁或内存访问冲突等问题。
例如,在创建并发运行时对象时,如果尚未创建计划程序,运行时会创建一个默认计划程序。 相应地,在全局对象构造期间创建的运行时对象将导致运行时创建此默认计划程序。 但是,此过程采用了内部锁,这会干扰支持并发运行时基础结构的其他对象的初始化过程。 另一个尚未初始化的基础结构对象可能需要此内部锁,因此会导致您的应用程序中发生死锁。
以下示例演示如何创建全局 concurrency::Scheduler 对象。 此模式不仅适用于 Scheduler 类,还适用于并发运行时提供的所有其他类型。 建议您不要遵循此模式,因为它可能会导致您的应用程序中出现意外行为。
// global-scheduler.cpp
// compile with: /EHsc
#include <concrt.h>
using namespace concurrency;
static_assert(false, "This example illustrates a non-recommended practice.");
// Create a Scheduler object at global scope.
// BUG: This practice is not recommended because it can cause deadlock.
Scheduler* globalScheduler = Scheduler::Create(SchedulerPolicy(2,
MinConcurrency, 2, MaxConcurrency, 4));
int wmain()
{
}
不要在共享数据段中使用并发对象
并发运行时不支持在共享数据部分,例如,由 data_seg#pragma 指令创建的数据部分,使用并发对象。 跨进程边界共享的并发对象可能会导致运行时处于不一致或无效状态。