[C/C++11]_[WTL/ATL]_[多线程如何无锁更新进度条界面上的描述字符串]

场景

  1. 开发界面程序WTL/Win32时,经常会使用进度条来实时显示百分比和描述文字。这些文字一般都用一个成员变量wstring来存储,而文字的更新可能比较频繁,比如下载复制文件时,显示正在复制的文件名。可能500ms就需要更新一次文字。 绘制是在主线程操作,那么更新和读取得常见做法是工作线程发送消息到需要更新的文本到主线程,之后主线程取出消息并更新文本。这么做的缺陷就是需要发送的消息太多了。有没有更好的办法?

说明

  1. 如果是多线程更新变量,那么只有两种做法,一个是加互斥量(加锁)更新,一个是无锁更新。

    • 加锁更新的缺点很明显,在读写的时候都需要加锁,大概率会导致主线程卡顿,肯定的是CPU资源是损耗很多的。
    • 另外一个就是无锁更新,在工作线程直接调用无锁更新变量的方法。 无锁更新一个变量在C++11里提供了atomic_loadatomic_store的方法。
  2. atomic_loadatomic_store都需要参数shared_ptr<T>来操作,本质上是对指针进行操作。也就是可以对shared_ptr<wstring>变量类型操作。

  3. atomic_loadatomic_store也可以使用volatile数值类型或者atomic<T>类型,而T如果对象类型只支持扁平对象,如果是atomic<string>编译会报错

error C2338: atomic<T> requires T to be trivially copyable.
  1. atomic_load是原子复制一个shared_ptr<T>并返回,我们多线程操作计数+1的共享指针并没有问题,即使旧的shared_ptr<T>销毁。 它复制shared_ptr<T>是原子的,这里说明一个问题,多线程修改shared_ptr<T>共享变量并不是原子的,即对shared_ptr的计数+1-1。 注意,不能再使用=来进行复制共享指针。

  2. volatile变量更新不是原子的,有可能多线程在某一个时刻修改这个变量导致值丢失。可以看例子的add变量之所以用atomic<int64>而不是volatile int64_t就是因为这个原因。

string get() {
	auto one = atomic_load(&desc_);
	return (one) ? *one.get() : "";
}
  1. 对应修改的方法是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

参考

  1. std::atomic_load

  2. std::atomic_store

  3. std::shared_ptr

相关推荐

  1. 线(42)编程

    2024-03-11 00:30:05       38 阅读
  2. WPF更新UI线实现进度功能

    2024-03-11 00:30:05       23 阅读
  3. C# 线编程:线并发

    2024-03-11 00:30:05       34 阅读
  4. 总结线各种

    2024-03-11 00:30:05       58 阅读
  5. 线(策略, synchronized 对应策略)

    2024-03-11 00:30:05       44 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-11 00:30:05       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-11 00:30:05       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-11 00:30:05       87 阅读
  4. Python语言-面向对象

    2024-03-11 00:30:05       96 阅读

热门阅读

  1. 【LeetCode周赛】第388场周赛

    2024-03-11 00:30:05       41 阅读
  2. spring常见面试题

    2024-03-11 00:30:05       44 阅读
  3. MacOS安装反编译工具JD-GUI 版本需要1.8+

    2024-03-11 00:30:05       36 阅读