场景
- 开发界面程序
WTL/Win32
时,经常会使用进度条来实时显示百分比和描述文字。这些文字一般都用一个成员变量wstring
来存储,而文字的更新可能比较频繁,比如下载复制文件时,显示正在复制的文件名。可能500ms
就需要更新一次文字。 绘制是在主线程操作,那么更新和读取得常见做法是工作线程发送消息到需要更新的文本到主线程,之后主线程取出消息并更新文本。这么做的缺陷就是需要发送的消息太多了。有没有更好的办法?
说明
如果是多线程更新变量,那么只有两种做法,一个是加互斥量(加锁)更新,一个是无锁更新。
- 加锁更新的缺点很明显,在读写的时候都需要加锁,大概率会导致主线程卡顿,肯定的是
CPU
资源是损耗很多的。 - 另外一个就是无锁更新,在工作线程直接调用无锁更新变量的方法。 无锁更新一个变量在
C++11
里提供了atomic_load
和atomic_store
的方法。
- 加锁更新的缺点很明显,在读写的时候都需要加锁,大概率会导致主线程卡顿,肯定的是
atomic_load
和atomic_store
都需要参数shared_ptr<T>
来操作,本质上是对指针进行操作。也就是可以对shared_ptr<wstring>
变量类型操作。atomic_load
和atomic_store
也可以使用volatile
数值类型或者atomic<T>
类型,而T
如果对象类型只支持扁平对象,如果是atomic<string>
编译会报错
error C2338: atomic<T> requires T to be trivially copyable.
atomic_load
是原子复制一个shared_ptr<T>
并返回,我们多线程操作计数+1
的共享指针并没有问题,即使旧的shared_ptr<T>
销毁。 它复制shared_ptr<T>
是原子的,这里说明一个问题,多线程修改shared_ptr<T>
共享变量并不是原子的,即对shared_ptr
的计数+1
或-1
。 注意,不能再使用=
来进行复制共享指针。volatile
变量更新不是原子的,有可能多线程在某一个时刻修改这个变量导致值丢失。可以看例子的add
变量之所以用atomic<int64>
而不是volatile int64_t
就是因为这个原因。
string get() {
auto one = atomic_load(&desc_);
return (one) ? *one.get() : "";
}
- 对应修改的方法是
atomic_store
原子修改一个shared_ptr
共享指针的实际对象。注意,不能再使用=
来进行复制共享指针。
void update(const char* desc) {
auto now = make_shared<string>("world");
atomic_store(&desc_, now);
}
例子
// test-atomic-modify-string.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <atomic>
#include <thread>
#include <string>
#include <memory>
using namespace std;
struct Si
{
int i;
int i1;
int i2;
};
class Dialog
{
public:
Dialog() {
desc_ = make_shared<string>("hello");
}
string get() {
auto one = atomic_load(&desc_);
return (one) ? *one.get() : "";
}
void update(const char* desc) {
auto now = make_shared<string>("world");
atomic_store(&desc_, now);
}
private:
shared_ptr<string> desc_;
};
static const int gTextThreadNum = 10;
void TestAtomicModifyPointerString()
{
// 编译错误: error C2338: atomic<T> requires T to be trivially copyable.
// atomic的特化类型必须是扁平结构, 比如C结构体
// atomic<string> str;
// 需要sizeof()大于8
// atomic<Si> ssi;
// volatile : 写不是原子的
Dialog dialog;
std::thread threads[gTextThreadNum];
atomic<int64_t> add(0);
auto func = [&add](Dialog* dialog) {
for (auto i = 0; i < 10000; ++i) {
auto temp = dialog->get();
if (temp.size())
add.fetch_add(1);
dialog->update((i % 2)?"hello":"world");
}
};
cout << "---- synchronized ----" << endl;
for (auto i = 0; i < gTextThreadNum; ++i)
threads[i] = move(std::thread(func,&dialog));
for (auto i = 0; i < gTextThreadNum; ++i)
threads[i].join();
cout << dialog.get() << " -> " << add.load() << endl;
}
int main()
{
std::cout << "Hello World!\n";
TestAtomicModifyPointerString();
}
输出
Hello World!
---- synchronized ----
world -> 100000