base::WeakPtr 在 Chromium 中 不能安全地跨线程使用。这是一个很关键的点,下面详细解释原因及正确用法。
🔍原理与使用
✅ 先说答案:
- base::WeakPtr 本质上是**线程绑定(thread-affine)**的。
- 不能在多个线程之间创建或使用它,特别是不能在一个线程创建 WeakPtr,然后在另一个线程中使用它。
- 如果你非要跨线程判断对象是否有效,应使用更底层或线程安全的机制(例如 RefCountedThreadSafe + scoped_refptr)。
📌 官方说明
Chromium 的 base::WeakPtr 文档中有明确说明(可在源码注释中看到):
WeakPtr is not thread-safe. It must be used and dereferenced on the same thread (or Sequence) on which it was obtained from a WeakPtrFactory.
这段代码是chrome源码里面的WeakPtr实现:
WeakPtr<T> GetWeakPtr() { return WeakPtr<T>(weak_reference_owner_.GetRef(), reinterpret_cast<T*>(ptr_)); }
是否线程安全?答案是: ❌ 不是线程安全的,除非你明确知道 weak_reference_owner_
是线程安全的,而且 ptr_
的生命周期也受线程安全保护(比如引用计数等)。
🔍 逐行分析:
1. weak_reference_owner_.GetRef()
这看起来是一个类似 base::internal::WeakReferenceOwner
或你自定义的 WeakReferenceOwner
类型。Chromium 中的 WeakReferenceOwner
默认是 线程绑定的(Thread-affine),只能在创建它的线程中使用(否则在 Debug 模式下会崩)。
✅ 所以:
GetRef()
调用必须在原始线程中,不是线程安全的。
2. reinterpret_cast<T*>(ptr_)
这个操作没做线程安全保护的话,也是不安全的,尤其是如果 ptr_
不是线程安全引用类型(如 scoped_refptr<T>
)。
✅ 正确的 WeakPtr
创建姿势(来自 Chromium)
Chromium 推荐用 base::WeakPtrFactory<T>
,并且只能在绑定线程上使用它:
class MyClass { public: base::WeakPtr<MyClass> GetWeakPtr() { return weak_factory_.GetWeakPtr(); // ✅ 安全:只能在同一个线程上用 } private: base::WeakPtrFactory<MyClass> weak_factory_{this}; };
-
WeakPtrFactory<T>
本身有一个SequenceChecker
,确保你只能在创建线程中用GetWeakPtr()
。 -
即使你多线程中有多个任务,都只能在同一个线程中访问和使用这个
WeakPtrFactory
。
❌ 错误使用示例
如果你把 GetWeakPtr()
这个函数暴露给了多个线程,并且多个线程中调用了它,比如这样:
thread1: my_object->GetWeakPtr(); thread2: my_object->GetWeakPtr();
即便 GetWeakPtr()
本身没有 crash,也会存在竞争风险或触发 DCHECK。
✅ 如果你真的想跨线程构建 WeakPtr...
你可以:
-
在初始化线程上调用
GetWeakPtr()
。 -
然后把这个 WeakPtr 通过
PostTask
发送到其他线程使用(读取、检查是否有效)。 -
但不能在其他线程上继续调用
GetWeakPtr()
。
✅ 总结表格
行为 | 是否线程安全 | 建议 |
---|---|---|
同一线程调用 GetWeakPtr() | ✅ 安全 | ✅ 推荐 |
不同线程调用 GetWeakPtr() | ❌ 不安全 | ❌ 禁止 |
一个线程创建 WeakPtr,另一个线程只用它 | ⚠️ 有条件安全 | ⚠️ 小心使用 |
自己实现 GetWeakPtr() 如你代码中那样 | ❌ 不建议 | ❌ 风险高 |
如你有多线程需求,建议考虑:
-
使用
scoped_refptr<T>
(跨线程完全安全); -
或将
WeakPtrFactory
迁移到后台线程中,并保证所有访问都在同一个线程; -
或用
CrossThreadWeakPtr
模式(Chromium中个别模块使用,较复杂)。
这段代码的作用是 在回调或异步任务执行时,确保 weak_self
所指向的对象仍然有效(未被销毁),以防止访问已经释放的对象从而导致 野指针或崩溃。
🔍 代码解析:
if (!weak_self || weak_self.WasInvalidated()) { return; }
这是一个典型的 base::WeakPtr<T>
使用场景,常用于异步任务或跨线程操作中。
✅ 判断目的:
-
!weak_self
-
检查
weak_self
是否为空(即未绑定任何对象)。 -
这相当于检查 WeakPtr 是否有效初始化。
-
-
weak_self.WasInvalidated()
-
判断该 WeakPtr 是否已经 失效(即指向的原始对象已经销毁)。
-
即使指针本身不为空,如果
T
已被析构,也不能再访问。
-
🛡️ 为什么这么写?
异步/延迟任务常见问题:
举个例子:
// 假设这是某个类中的成员函数 PostTask(FROM_HERE, base::BindOnce(&MyClass::DoWork, weak_factory_.GetWeakPtr()));
-
如果
MyClass
在任务执行前被析构,那么DoWork()
中的this
就是悬空指针。 -
WeakPtr
就是防止“任务晚于对象生命周期”这种典型 bug。 -
所以在
DoWork()
内部首先要判断weak_self
是否有效。
🧠 小结:
if (!weak_self || weak_self.WasInvalidated()) { return; // 安全退出,防止访问已销毁对象 }
这个判断非常关键,用于:
-
保证异步回调安全性;
-
防止 use-after-free;
-
是 C++ 客户端编程(尤其是 Chromium 项目)中的推荐模式。
🛡️ 工程案例:
cookie_manager->SetCanonicalCookie(*canonical_cookie, cookie_info.url, options, base::BindOnce(&InProcessImporterBridge::SetCanonicalCookieCallback, weak_factory_.GetWeakPtr())); 这个代码在ui线程,SetCanonicalCookieCallback这个回调要是在其他线程调用会有什么问题\
在 Chromium 的线程模型中,SetCanonicalCookieCallback
回调若在其他线程(非 UI 线程)调用可能导致严重问题。以下是详细分析:
问题根源
-
线程安全性违例
-
InProcessImporterBridge
类及其成员变量通常非线程安全,假设仅在 UI 线程访问。 -
若回调在其他线程(如 IO 线程)执行,直接访问成员变量会导致数据竞争(Data Race)。
-
-
weak_factory_.GetWeakPtr()
的线程安全性-
WeakPtrFactory
本身线程安全(GetWeakPtr()
可跨线程调用),但不保证回调执行时的对象存活。 -
若 UI 线程已销毁
InProcessImporterBridge
,其他线程调用回调会访问无效内存。
-
-
UI 线程依赖的操作
-
回调中若操作 UI 相关资源(如更新界面、访问
Profile
),必须在 UI 线程执行。
-
具体风险场景
// 错误示例:回调可能在其他线程执行 cookie_manager->SetCanonicalCookie( *canonical_cookie, cookie_info.url, options, base::BindOnce(&InProcessImporterBridge::SetCanonicalCookieCallback, weak_factory_.GetWeakPtr())); // 若回调在非UI线程运行
-
崩溃风险:
-
其他线程调用时,若
InProcessImporterBridge
已销毁,weak_factory_
返回nullptr
,回调被丢弃(安全)。 -
但若对象存活,回调直接访问成员变量(如
std::vector
、std::map
)会触发未定义行为。
-
-
逻辑错误:
-
非 UI 线程修改 UI 相关状态(如界面标志位),导致界面显示异常。
-
解决方案
1. 强制回调到 UI 线程
使用 base::BindPostTask
将回调派发到 UI 线程的任务队列:
cookie_manager->SetCanonicalCookie( *canonical_cookie, cookie_info.url, options, base::BindOnce( base::BindPostTask( base::SequencedTaskRunner::GetCurrentDefault(), // 获取UI线程TaskRunner base::BindOnce(&InProcessImporterBridge::SetCanonicalCookieCallback, weak_factory_.GetWeakPtr()) ) ) );
2. 回调中显式检查线程
在回调内部验证是否运行在 UI 线程:
void InProcessImporterBridge::SetCanonicalCookieCallback(...) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // 安全操作成员变量 }
3. 使用 ThreadSafeRefCounted
(若适合)
若 InProcessImporterBridge
需跨线程共享,改为继承 base::RefCountedThreadSafe
,但需确保所有成员访问均线程安全(通常不推荐用于 UI 相关类)。
为什么 WeakPtr
不能完全解决问题?
-
WeakPtr
仅保证对象销毁后不执行回调,但若对象存活:-
不保证回调线程安全。
-
不自动切换线程上下文。
-
-
必须配合
BindPostTask
或线程检查使用。
Chromium 的线程模型要求
-
UI 线程专属对象
-
如
Profile
、WebContents
、UI 控件等,必须通过 UI 线程访问。
-
-
线程跳转代价
-
跨线程任务提交(如
PostTask
)有开销,但比崩溃/数据竞争更可接受。
-
正确代码示例
// 方法1:使用 BindPostTask(推荐) cookie_manager->SetCanonicalCookie( *canonical_cookie, cookie_info.url, options, base::BindPostTask( base::SequencedTaskRunner::GetCurrentDefault(), base::BindOnce(&InProcessImporterBridge::SetCanonicalCookieCallback, weak_factory_.GetWeakPtr()) ) ); // 方法2:回调内手动跳转(备用) class InProcessImporterBridge { public: void SetCanonicalCookieCallback(...) { if (!base::SequencedTaskRunner::GetCurrentDefault()->RunsTasksInCurrentSequence()) { base::SequencedTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&InProcessImporterBridge::SetCanonicalCookieCallback, weak_factory_.GetWeakPtr(), ...)); return; } DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // 实际处理逻辑 } };
总结
问题 | 风险等级 | 解决方案 |
---|---|---|
非UI线程访问成员变量 | 高(崩溃) | 用 BindPostTask 跳转回 UI 线程 |
WeakPtr 对象已销毁 | 低(安全) | 自动跳过回调 |
未同步的 UI 状态修改 | 中(逻辑错误) | 添加 DCHECK_CALLED_ON_VALID_SEQUENCE |
最佳实践:
-
默认假设回调可能在任何线程执行。
-
对 UI 相关对象,始终通过
BindPostTask
或PostTask
确保线程安全。
下面是个跨线程场景使用的一个完整例子
class QQBrowserImporter : public Importer {
public:
QQBrowserImporter();
explicit QQBrowserImporter(bool first_run);
void StartImport(const importer::SourceProfile& source_profile,
uint16_t items,
ImporterBridge* bridge) override;
static void ImportCookies(const std::wstring& localPath, base::WeakPtr<QQBrowserImporter> client);
protected:
friend class base::RefCountedThreadSafe<QQBrowserImporter>;
~QQBrowserImporter() override;
private:
scoped_refptr<base::SequencedTaskRunner> GetdbTaskRunner();
bool ScheduleTask(const base::RepeatingClosure& task);
scoped_refptr<base::SequencedTaskRunner> db_thread_runner_;
base::WeakPtrFactory<QQBrowserImporter> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(QQBrowserImporter);
};
QQBrowserImporter::QQBrowserImporter(bool first_run) : first_run_(first_run), weak_factory_(this) {}
QQBrowserImporter::~QQBrowserImporter(void){}
void QQBrowserImporter::StartImport(const importer::SourceProfile& source_profile,
uint16_t items,
ImporterBridge* bridge) {
bridge_ = bridge;
bridge_->NotifyStarted();
std::wstring source_path = source_profile.source_path.value();
if ((items & importer::COOKIES) && !cancelled()) {
ScheduleTask(base::BindRepeating(&QQBrowserImporter::ImportCookies, source_path, weak_factory_.GetWeakPtr()));
}
}
// TODO extract to base class
void QQBrowserImporter::ImportCookies(const std::wstring& localPath, base::WeakPtr<QQBrowserImporter> client) {
std::wstring local_data_path = localPath;
std::string desc;
local_data_path += L"\\Default\\Network\\Cookies";
base::FilePath qq_login_path(local_data_path);
sql::Database db;
if (!db.Open(qq_login_path)) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce([](base::WeakPtr<QQBrowserImporter> weak_self) {
if (!weak_self || weak_self.WasInvalidated()) {
return;
}
if (weak_self->bridge_) {
base::Value::Dict import_res;
import_res.Set("successcount", 0);
import_res.Set("faileddesc", "Failed to open QQ login data DB");
std::string value_str;
base::JSONWriter::Write(import_res, &value_str);
weak_self->bridge_->NotifyEnded(false, importer::TYPE_COOKIES_QQ, value_str);
}
},
client));
return;
}
const char* kQuery = "SELECT host_key, encrypted_value, name, path, creation_utc, expires_utc, last_access_utc, is_secure, is_httponly, samesite, priority FROM cookies";
sql::Statement stmt(db.GetUniqueStatement(kQuery));
if (!stmt.is_valid()) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce([](base::WeakPtr<QQBrowserImporter> weak_self) {
if (!weak_self || weak_self.WasInvalidated()) {
return;
}
if (weak_self->bridge_) {
base::Value::Dict import_res;
import_res.Set("successcount", 0);
import_res.Set("faileddesc", "Failed to prepare login query statement");
std::string value_str;
base::JSONWriter::Write(import_res, &value_str);
weak_self->bridge_->NotifyEnded(false, importer::TYPE_COOKIES_QQ, value_str);
}
},
client));
return;
}
std::string localPathUtf8 = base::WideToUTF8(localPath);
auto aesKey = chrome::GetDecryptedKey(localPathUtf8);
std::vector<importer::CookiesInfo> cookies_info;
while (stmt.Step()) {
std::string host_key = stmt.ColumnString(0);
std::string name = stmt.ColumnString(2);
std::string path = stmt.ColumnString(3);
int64_t creation_utc = stmt.ColumnInt64(4);
int64_t expires_utc = stmt.ColumnInt64(5);
int64_t last_access_utc = stmt.ColumnInt64(6);
// compatible with old browser kernels, new kernels will add SameSite=None by default
int is_http_only = true; // stmt.ColumnInt(8);
int is_secure = true; // stmt.ColumnInt(7);
int samesite_val = stmt.ColumnInt(9);
int priority_val = stmt.ColumnInt(10);
base::span<const uint8_t> blob_span = stmt.ColumnBlob(1);
std::vector<unsigned char> encrypted(blob_span.begin(), blob_span.end());
std::string value;
if (encrypted.size() > 15 && encrypted[0] == 'v' && encrypted[1] == '1') {
std::vector<unsigned char> nonce(encrypted.begin() + 3, encrypted.begin() + 15);
std::vector<unsigned char> ciphertext(encrypted.begin() + 15, encrypted.end() - 16);
std::vector<unsigned char> tag(encrypted.end() - 16, encrypted.end());
auto decrypted = chrome::AESGCMDecrypt(aesKey, nonce, ciphertext, tag);
value = std::string(decrypted.begin(), decrypted.end());
}
else {
continue;
}
std::string cookie_str = FormatCookieString(
name, value, host_key, path, expires_utc, is_secure == 0 ? false : true, is_http_only == 0 ? false : true, samesite_val
);
std::string scheme = is_secure ? "https://" : "http://";
GURL url(scheme + ConvertHostKeyToDomain(host_key) + path);
cookies_info.emplace_back(std::move(cookie_str), std::move(url), is_http_only);
}
if (cookies_info.empty()) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce([](base::WeakPtr<QQBrowserImporter> weak_self) {
if (!weak_self || weak_self.WasInvalidated()) {
return;
}
if (weak_self->bridge_) {
base::Value::Dict import_res;
import_res.Set("successcount", 0);
import_res.Set("faileddesc", se_import_user_info::kEmptyData);
std::string value_str;
base::JSONWriter::Write(import_res, &value_str);
weak_self->bridge_->NotifyEnded(false, importer::TYPE_COOKIES_QQ, value_str);
}
},
client));
return;
}
auto task = base::BindOnce([](base::WeakPtr<QQBrowserImporter> weak_self, std::vector<importer::CookiesInfo> cookies_info) {
if (!weak_self || weak_self.WasInvalidated()) {
return;
}
if (weak_self->bridge_) {
weak_self->bridge_->SetCookie(cookies_info, se_import_user_info::BrowserType::kQQ, importer::TYPE_COOKIES_QQ);
}
}, client, cookies_info);
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(task));
}
scoped_refptr<base::SequencedTaskRunner> QQBrowserImporter::GetdbTaskRunner() {
if (!db_thread_runner_) {
db_thread_runner_ = base::ThreadPool::CreateSingleThreadTaskRunner(
{base::TaskPriority::HIGHEST, base::MayBlock()});
}
return db_thread_runner_;
}
bool QQBrowserImporter::ScheduleTask(const base::RepeatingClosure& task) {
scoped_refptr<base::SequencedTaskRunner> task_runner(GetdbTaskRunner());
if (task_runner.get())
return task_runner->PostTask(FROM_HERE, task);
return false;
}