并发问题:如何只有一个线程通过关键部分
Concurrency Problem: how to have only one thread go through critical section
我想要一个多线程函数,为对象obj
分配一些内存并返回分配的内存。下面是我当前的单线程和多线程版本代码。
多线程版本没有竞争条件,但当很多线程试图获得锁时,运行速度很慢。在malloc
和指针更新之后,每个线程仍然需要获取和释放相同的锁。这会导致多线程性能下降。我想知道是否还有其他方法可以提高性能。
struct multi_level_tree{
multi_level_tree* ptr[256];
mutex mtx;
};
multi_level_tree tree; // A global object that every thread need to access and update
/* Single Threaded */
multi_level_tree* get_ptr(multi_level_tree* cur, int idx) {
if (!cur[idx].ptr)
cur[idx].ptr = malloc(sizeof(T));
return cur[idx].ptr;
}
/* Multi Threaded with mutex */
void get_ptr(multi_level_tree* cur, int idx) {
if (!cur[idx].ptr) {
cur[idx].mtx.lock(); // other threads wait here, and go one by one
/* Critical Section Start */
if (!cur[idx].ptr)
cur[idx].ptr = malloc(sizeof(multi_level_tree)); // malloc takes a while
/* Critical Section End */
cur[idx].mtx.unlock();
}
return cur[idx].ptr;
}
我正在查找的代码应该具有以下属性。
- 当第一个线程分配内存时,它应该提醒所有等待它的其他线程
- 所有其他线程应同时解除阻塞
- 无比赛条件
问题中的挑战*树是稀疏的,有多个级别,考虑到我们的内存,不可能初始化所有这些*类似于双重检查锁定问题,但试图避免std::atomic
此代码的重点是将多级数组实现为全局变量。除了最低级别之外,每个数组都是指向下一级别数组的指针列表。由于这个数据结构需要动态增长,所以我遇到了这个问题。
如何只有一个线程通过关键部分
您可以使用互斥锁。你的问题中有一个例子。
这不是同步认证的最佳解决方案。一个简单的改进是使用本地静态,在这种情况下,编译器负责实现同步:
T& get_T() {
static T instance;
return instance;
}
但当许多线程试图获得锁定时运行缓慢
这个问题是串行访问同一数据结构所固有的。提高性能的一种方法是从一开始就避免这样做。
在这个特定的例子中,您可以在进程仍然是单线程的时候简单地初始化资源,并且只有在初始化完成后才启动并行线程。这样,访问指针就不需要锁定。
如果这不是一个选项,另一种方法是在每个线程中简单地调用get_ptr
一次,并在本地存储一个副本。这样,锁定开销保持最小。
更好的做法是在每个线程中都有单独的数据结构。当线程只生成数据,而不需要访问其他线程的结果时,这很有用。
关于编辑示例:您可能会从无锁树实现中受益。然而,这可能很难实施。
由于您无法轻松修复它,因为它是并发的固有特性,因此我有一个想法,可以通过来大幅提高或降低性能。
如果这个资源真的经常被使用并且是有害的,你可以尝试使用活动对象(https://en.wikipedia.org/wiki/Active_object)和Boost无锁定队列(https://www.boost.org/doc/libs/1_66_0/doc/html/lockfree/reference.html#header.boost.lockfree.queue_hpp)。在Future对象上使用原子存储/加载,您将使此过程完全无锁定。但另一方面,它将需要一个单独的线程来维护。这种解决方案的性能在很大程度上取决于此资源的使用频率。
从@WilliamClements的评论表单中,我发现这本身就是一个双重检查的锁定问题。我的问题中的原始多线程代码可能已损坏。为了正确编程,我切换到原子指针,以防止加载/存储指令出现排序问题。
但是,该示例仍然使用了一个我想去掉的锁。因此,我选择使用std::atomic::compare_exchange_weak
只在指针值为nullptr时更新指针。通过这种方式,只有一个线程将成功更新指针值,而如果std::atomic::compare_exchange_weak
失败,其他线程将释放请求的内存。
到目前为止,这个代码对我来说做得很好。
struct multi_level_tree{
std::atomic<multi_level_tree*> ptr;
};
multi_level_tree tree;
void get_ptr(multi_level_tree* cur, int idx) {
if (!cur[idx].ptr.load()) {
/* Critical Section Start */
if (!cur[idx].ptr.load()) {
node* tmp = malloc(sizeof(multi_level_tree)*256);
if (cur[idx].ptr.compare_exchange_weak(nullptr, tmp)) {
/* successfully updated, do nothing */
}
else {
/* Already updated by other threads, release */
free(tmp);
}
}
/* Critical Section End */
}
return cur[idx].ptr;
}
- 从不同线程使用int64的不同字节安全吗
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 在C++中使用cURL和多线程
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在cuda线程之间共享大量常量数据
- 如何将元素添加到数组的线程安全函数?
- 线程,如果else语句,都是错误的上下文切换后,会发生什么
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 异常属于C++中的线程还是进程
- C++中的线程安全删除
- C++使用params创建线程函数会导致转换错误
- 类与私有变量的其他类之间的线程安全性
- 更新与多个线程unordred_map中的不同现有键对应的值
- MPI - 当数组初始化值必须为常量时,如何为工作线程创建部分数组
- C++ stl unordered_map,线程安全,其中每个线程仅访问其自己分配的键,并且可以编辑该值
- 如果不同的线程始终使用不同的键,它们是否可以插入到映射中
- 左键需要作为赋值的左操作数?查看其他线程无济于事
- 只有当我在 c++ 中按 Enter 键(线程)时,代码如何进入下一步
- 三十部分库中的线程安全性