我遇到了一个(基本的)自旋锁互斥锁的问题,似乎没有按预期工作.
4个线程正在递增受此互斥锁保护的非原子计数器.结果与使互斥体看起来破碎的预期结果不匹配.
示例输出:
result: 2554230
expected: 10000000
Run Code Online (Sandbox Code Playgroud)
在我的环境中,它发生在以下条件下:
flag是的std::atomic<bool>,其他任何东西,如std::atomic<int>或std::atomic_flag(和test_and_set)工作正常.
使用gcc 6.3.1和-O3flag 在X86_64上编译
我的问题是,有什么可以解释这种行为?
#include <iostream>
#include <vector>
#include <atomic>
#include <thread>
#include <mutex>
#include <assert.h>
class my_mutex {
std::atomic<bool> flag{false};
public:
void lock()
{
while (flag.exchange(true, std::memory_order_acquire));
}
void unlock()
{
flag.store(false, std::memory_order_release);
}
};
my_mutex mut;
static int counter = 0;
void increment(int cycles)
{
for (int i=0; i < cycles; ++i)
{
std::lock_guard<my_mutex> lck(mut);
++counter;
}
} …Run Code Online (Sandbox Code Playgroud) decltype(auto) func(int&& n)
{
return (n);
}
Run Code Online (Sandbox Code Playgroud)
clang 12.0.1接受此代码,但gcc 11.2.1拒绝它:
error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
Run Code Online (Sandbox Code Playgroud)
gcc似乎使用 return type int &&,但带有n内括号,这是正确的吗?
boost::shared_mutex或std::shared_mutex(C++ 17)可用于单个写入器,多个读取器访问.作为一项教育练习,我将一个使用自旋锁的简单实现放在一起并具有其他限制(例如公平策略),但显然不打算在实际应用中使用.
我们的想法是,如果没有线程持有锁,则互斥锁保持引用计数为零.如果> 0,则该值表示有权访问的读者数.如果为-1,则单个编写者可以访问.
这是一个没有数据竞争的正确实现(特别是使用的,最小的内存排序)吗?
#include <atomic>
class my_shared_mutex {
std::atomic<int> refcount{0};
public:
void lock() // write lock
{
int val;
do {
val = 0; // Can only take a write lock when refcount == 0
} while (!refcount.compare_exchange_weak(val, -1, std::memory_order_acquire));
// can memory_order_relaxed be used if only a single thread takes write locks ?
}
void unlock() // write unlock
{
refcount.store(0, std::memory_order_release);
}
void lock_shared() // read lock
{
int val;
do {
do …Run Code Online (Sandbox Code Playgroud) 考虑这个代码,where x和y是整数:
if (x)
y = 42;
Run Code Online (Sandbox Code Playgroud)
是否允许以下编译器转换?
int tmp = y;
y = 42;
if (!x)
y = tmp;
Run Code Online (Sandbox Code Playgroud)
背景:
这是来自Bjarne Stroustrup的FAQ:
// start with x==0 and y==0
if (x) y = 1; // Thread 1
if (y) x = 1; // Thread 2
Run Code Online (Sandbox Code Playgroud)
常见问题解答说这是数据竞争免费; 使用x和y0都不应该写入任何变量.
但如果允许转换怎么办?
比如说,我Foo在线程#1中创建了一个类型的对象,并希望能够在线程#3中访问它.
我可以尝试这样的事情:
std::atomic<int> sync{10};
Foo *fp;
// thread 1: modifies sync: 10 -> 11
fp = new Foo;
sync.store(11, std::memory_order_release);
// thread 2a: modifies sync: 11 -> 12
while (sync.load(std::memory_order_relaxed) != 11);
sync.store(12, std::memory_order_relaxed);
// thread 3
while (sync.load(std::memory_order_acquire) != 12);
fp->do_something();
Run Code Online (Sandbox Code Playgroud)
Foo,更新为11sync增加到12该场景被破坏,因为线程#3旋转直到它加载12,其可能无序到达(wrt 11)并且Foo不与12一起订购(由于线程#2a中的放松操作).
这有点违反直觉,因为修改顺序sync是10→11→12
标准说(§1.10.1-6):
原子存储 - 释放与从存储中获取其值的load-acquire同步(29.3).[注意:除了在指定的情况下,读取更高的值不一定能确保可见性,如下所述.这样的要求有时会干扰有效的实施. - 尾注]
它也在(§1.10.1-5)中说:
由原子对象M上的释放操作A引导的释放序列是M的修改顺序中的副作用的最大连续子序列,其中第一操作是A,并且每个后续操作
- 由执行A的相同线程执行. ,或
- 是原子读 - 修改 - 写操作.
现在,线程#2a被修改为使用原子读 - 修改 …
在这个例子中,一个foo实例除了打印它是复制还是移动构造之外什么都不做.
#include <iostream>
#include <algorithm>
#include <vector>
struct foo {
foo()=default;
foo(foo &&) { std::cout << "move constructed\n"; }
foo(const foo &) { std::cout << "copy constructed\n"; }
};
int main()
{
foo x;
std::vector<int> v; // empty
std::remove_if(v.begin(), v.end(),
[x=std::move(x)](int i){ return false; });
}
Run Code Online (Sandbox Code Playgroud)
这会产生以下输出:
move constructed
copy constructed
move constructed
move constructed
copy constructed
copy constructed
Run Code Online (Sandbox Code Playgroud)
问题:
std::remove_if创建这么多的闭包?编译器是gcc 8.1.1
考虑以下代码:
struct T { std::atomic<int> a = 2; };
T* t = new T();
// Thread 1
if(t->a.fetch_sub(1,std::memory_order_relaxed) == 1)
delete t;
// Thread 2
if(t->a.fetch_sub(1,std::memory_order_relaxed) == 1)
delete t;
Run Code Online (Sandbox Code Playgroud)
我们确切地知道线程 1 和线程 2 之一将执行delete. 但是我们安全吗?我的意思是假设线程 1 将执行delete. 它是保证当线程1开始的delete,线程2甚至不会看t?
您能否举一个真实的例子,std::atomic::compare_exchange出于某种原因使用两个memory_order参数版本(因此一个memory_order参数版本不合适)?
如果对象在线程之间正确同步,是否允许调用join()在std::thread不同线程中创建的对象?
例如:
void cleanup(std::thread t)
{
t.join();
}
int main()
{
std::thread t{[] { /* something */ }};
std::thread c{cleanup, std::move(t)};
c.join();
}
Run Code Online (Sandbox Code Playgroud) 这是用于演示的简单代码片段。
有人告诉我,双重检查锁不正确。由于变量是非易失性的,编译器可以自由地重新排序调用或优化它们(有关详细信息,请参阅 codereview.stackexchange.com/a/266302/226000)。
但是我真的看到很多项目中确实使用了这样的代码片段。有人可以对这个问题有所了解吗?我用谷歌搜索并和我的朋友谈论它,但我仍然找不到答案。
#include <iostream>
#include <mutex>
#include <fstream>
namespace DemoLogger
{
void InitFd()
{
if (!is_log_file_ready)
{
std::lock_guard<std::mutex> guard(log_mutex);
if (!is_log_file_ready)
{
log_stream.open("sdk.log", std::ofstream::out | std::ofstream::trunc);
is_log_file_ready = true;
}
}
}
extern static bool is_log_file_ready;
extern static std::mutex log_mutex;
extern static std::ofstream log_stream;
}
//cpp
namespace DemoLogger
{
bool is_log_file_ready{false};
std::mutex log_mutex;
std::ofstream log_stream;
}
Run Code Online (Sandbox Code Playgroud)
更新:谢谢大家。InitFd()确实有更好的实现,但它确实只是一个简单的演示,我真正想知道的是,双重检查锁定是否存在任何潜在问题。
有关完整的代码片段,请参阅https://codereview.stackexchange.com/questions/266282/c-logger-by-template。