struct X
{
std::mutex m;
std::string str;
void set(std::string s)
{
auto _ = std::unique_lock(m);
str = std::move(s);
}
~X()
{
// auto _ = std::unique_lock(m);
}
}
有没有标准的部分保证在没有注释行的情况下,
~X
在~string
内部永远不会出现竞态条件?通过具有RELAXED语义的原子变量来管理对象和/或生命周期的独占访问是可能的(即除了生命周期结束的事实之外,不同步任何其他数据)。我们有一个互斥锁来保护对对象的访问,因此使用松散操作似乎可以用于同步独占访问/生命周期和互斥锁来访问数据。
如果标准要求
~mutex
与最新的unlock
同步,并且我们将互斥锁的声明移到受保护数据的下方,那么我们可以使用默认的~X
,但如果没有这个要求,我们需要始终有显式的析构函数来锁定保护任何成员的所有互斥锁。
PS为了帮助理解我的问题,使用这个例子https://en.cppreference.com/w/cpp/thread/mutex,有一个评论说在没有锁的情况下访问g_pages
是安全的。为什么?标准的哪一部分保证了这一点?两个线程都加入只能保证对映射的多线程访问没有问题,但它不能保证与最新的mutex::unlock
操作同步。我运行了许多不同的程序,试图暴露出竞态条件,我非常确定mutex
在lock
和unlock
时使用了std::atomic_thread_fence
,它与必须使用至少一些原子操作的thread.join
同步。但问题是标准不要求mutex
使用std::atomic_thread_fence
,只是碰巧我所知道的所有实现都使用了它。
#include <chrono>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <thread>
std::map<std::string, std::string> g_pages;
std::mutex g_pages_mutex;
void save_page(const std::string &url)
{
// simulate a long page fetch
std::this_thread::sleep_for(std::chrono::seconds(2));
std::string result = "fake content";
std::lock_guard<std::mutex> guard(g_pages_mutex);
g_pages[url] = result;
}
int main()
{
std::thread t1(save_page, "http://foo");
std::thread t2(save_page, "http://bar");
t1.join();
t2.join();
// safe to access g_pages without lock now, as the threads are joined
for (const auto &pair : g_pages)
std::cout << pair.first << " => " << pair.second << '\n';
}
PPS正如@user17732522所指出的那样,上面的示例由于使用了
thread.join
,所以是绝对安全的。你可以移除互斥锁和其中一个线程(只使用一个线程),它仍然是安全的。因此,我稍微修改了这个示例来演示问题。请注意,这里的松散内存顺序是重要的。如果我们将其替换为一对acquire/release,那么它将成为完全线程安全的代码,但如果我们假设std::mutex::unlock
使用std::atomic_thread_fence
,那么使用松散内存顺序也是正确的。#include <atomic>
#include <chrono>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <thread>
std::map<std::string, std::string> g_pages;
std::mutex g_pages_mutex;
std::atomic_flag g_f1 = {};
std::atomic_flag g_f2 = {};
void save_page(const std::string& url, std::atomic_flag* flag)
{
// simulate a long page fetch
std::this_thread::sleep_for(std::chrono::seconds(2));
std::string result = "fake content";
{
std::lock_guard<std::mutex> guard(g_pages_mutex);
g_pages[url] = result;
}
flag->clear(std::memory_order_relaxed);
}
int main()
{
g_f1.test_and_set();
g_f2.test_and_set();
std::thread t1(save_page, "http://foo", &g_f1);
std::thread t2(save_page, "http://bar", &g_f2);
while (g_f1.test_and_set(std::memory_order_relaxed));
while (g_f2.test_and_set(std::memory_order_relaxed));
// whether it safe to access g_pages without lock now, as the threads signaled they are done?
for (const auto& pair : g_pages)
std::cout << pair.first << " => " << pair.second << '\n';
t1.join();
t2.join();
}
join
与线程的完成同步,线程中的其他所有内容都在完成之前被顺序执行。在那一点上,互斥锁不再相关。 - undefinedmemory_order_relaxed
;仅因为您的标志读取了更新的值,并不意味着您将观察到发生在g_pages
上的任何事情,因为您尚未建立g_pages
需要在标志循环终止之后被读取。 - undefined