Closed Liam0205 closed 4 years ago
https://github.com/idealvin/co/blob/71f920481ca8d2d44bd0a44a8668c9d795c23d03/base/rpc.cc#L79
在库里的实际使用,目前只搜到了这一处。按我的理解,这里用到的正是 thread_ptr
每次在新线程里都要初始化的特点;保证每个线程用到的随机数种子不一样。即下面起到 initialization 作用的这一行:
https://github.com/idealvin/co/blob/71f920481ca8d2d44bd0a44a8668c9d795c23d03/base/rpc.cc#L243
既然在每个线程里都要 initialization 一次,那为什么不在线程函数开始的时候,声明并构造一个单独的 Random
类型对象就好了呢?当然,这样就无法把 _rand
作为数据成员封装起来了。
换而言之,它最大的价值是扩充了 C++11 引入的 thread_local
关键字在数据成员上的作用。thread_local
作用在数据成员上时,要求数据成员必须是 static
的。这也就是说,同一个类在同一个线程里的对象,访问该数据成员时,就像访问全局变量一样。但 thread_ptr
能做到同一个类在同一个线程里的对象,访问该数据成员时,就像访问局部变量一样。
也就是说,如果我实现这样一个类模板 tls
,然后把 Random
或者 std::unique_ptr<Random>
当做模板参数丢进去,就能实现同样的效果了,而且还无需使用 platform-specific APIs:
#pragma once
#include <unordered_map>
#include <mutex>
#include <exception>
#include <utility>
namespace yuuki {
template <typename T>
struct tls {
private:
static thread_local std::unordered_map<const tls<T>*, T> tls_payloads;
mutable std::mutex mtx_;
public:
tls() = default;
explicit tls(const T& value) : tls() {
set(value);
}
explicit tls(T&& value) : tls() {
set(std::move(value));
}
~tls() {
std::lock_guard<std::mutex> lock(mtx_);
auto pair = tls_payloads.find(this);
if (pair != tls_payloads.end()) {
tls_payloads.erase(pair);
}
}
tls(const tls& other) : tls(other.get()) {}
tls(tls&& other) : tls(std::move(other.get())) {}
tls& operator=(const tls& other) {
set(other.get());
return *this;
}
tls& operator=(tls&& other) {
set(std::move(other.get()));
return *this;
}
void set(T&& value) {
std::lock_guard<std::mutex> lock(mtx_);
auto pair = tls_payloads.find(this);
if (pair == tls_payloads.end()) {
tls_payloads.emplace(this, std::move(value));
} else if (value == pair->second) {
return;
} else {
pair->second = std::move(value);
}
}
void set(const T& value) {
std::lock_guard<std::mutex> lock(mtx_);
auto pair = tls_payloads.find(this);
if (pair == tls_payloads.end()) {
tls_payloads.emplace(this, value);
} else if (value == pair->second) {
return;
} else {
pair->second = value;
}
}
T& get(void) const {
std::lock_guard<std::mutex> lock(mtx_);
auto pair = tls_payloads.find(this);
if (pair == tls_payloads.end()) {
throw std::logic_error("ERROR: fetching value before setting in current thread!");
} else {
return pair->second;
}
}
T get_copy(void) const {
return get();
}
};
template <typename T>
thread_local std::unordered_map<const tls<T>*, T> tls<T>::tls_payloads{};
} // namespace yuuki
线程是在框架内部启动的,实际业务处理不需要知道是在哪个线程中。
thread_ptr<Random>,不同线程需要设置不同的Random对象
---原始邮件---
发件人: "Liam Huang"<notifications@github.com>
发送时间: 2019年12月13日(星期五) 晚上7:32
收件人: "idealvin/co"<co@noreply.github.com>;
抄送: "Mention"<mention@noreply.github.com>;"Alvin"<idealvin@qq.com>;
主题: Re: [idealvin/co] 关于 thread_ptr
的疑问 (#31)
https://github.com/idealvin/co/blob/71f920481ca8d2d44bd0a44a8668c9d795c23d03/base/rpc.cc#L79
在库里的实际使用,目前只搜到了这一处。按我的理解,这里用到的正是 thread_ptr 每次在新线程里都要初始化的特点;保证每个线程用到的随机数种子不一样。即下面起到 initialization 作用的这一行:
https://github.com/idealvin/co/blob/71f920481ca8d2d44bd0a44a8668c9d795c23d03/base/rpc.cc#L243
既然在每个线程里都要 initialization 一次,那为什么不在线程函数开始的时候,声明并构造一个单独的 Random 类型对象就好了呢?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
那就是了。thread_ptr
起到的并不是指针的作用,而是 non-static
thread_local
data member 的作用。
也有指针的作用,thread_ptr用起来跟std::unique_ptr差不多,只是各线程都需要自行设置其值
---原始邮件---
发件人: "Liam Huang"<notifications@github.com>
发送时间: 2019年12月13日(星期五) 晚上8:30
收件人: "idealvin/co"<co@noreply.github.com>;
抄送: "Mention"<mention@noreply.github.com>;"Alvin"<idealvin@qq.com>;
主题: Re: [idealvin/co] 关于 thread_ptr
的疑问 (#31)
那就是了。thread_ptr 起到的并不是指针的作用,而是 non-static thread_local data member 的作用。
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
对的,原则上是这样,但是 map 加锁的实现效率不高
---原始邮件---
发件人: "Liam Huang"<notifications@github.com>
发送时间: 2019年12月13日(星期五) 晚上7:45
收件人: "idealvin/co"<co@noreply.github.com>;
抄送: "Mention"<mention@noreply.github.com>;"Alvin"<idealvin@qq.com>;
主题: Re: [idealvin/co] 关于 thread_ptr
的疑问 (#31)
换而言之,它最大的价值是扩充了 C++11 引入的 thread_local 关键字在数据成员上的作用。thread_local 作用在数据成员上时,要求数据成员必须是 static 的。这也就是说,同一个类在同一个线程里的对象,访问该数据成员时,就像访问全局变量一样。但 thread_ptr 能做到同一个类在同一个线程里的对象,访问该数据成员时,就像访问局部变量一样。
也就是说,如果我实现这样一个类 tls,然后把 Random 当做模板参数丢进去,就能实现同样的效果了:
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
额……其实我怀疑,pthread_key_t
内部实现也是带了 map 和锁的……
额……其实我怀疑,
pthread_key_t
内部实现也是带了 map 和锁的……
理论上不需要锁,可能是个数组,不是map.
可以参考 co::Mutex,co::Pool 的实现。
嗯,我看一下 co::Mutex
和 co::Pool
的实现。
还是没想明白为啥 pthread_key_t
内部不需要 map。它相当于要根据 pid
拿到不同指针,然后根据指针去找到真正存在 key 里的数据。如果是数组的话,那这个数组岂不是要很长……?要不然链表?那还不如用 map 呢?
看了一下 darwin_libpthread
的代码,pthread_key_t
内部是用一个 array 实现的 map 的功能。这个结构是 pthread_t
里的 void* tsd[]
,代码如下:
我之前一直疑惑的是,thread 的数量是不确定的,怎么用一个固定大小的 array 解决这个问题呢?看到注释我就笑了……人家根本就没解决哈哈哈~
然后在 pthread_key_create
的时候,把所有可能用上的 thread,都 create 了一遍。
@idealvin 你好。
按我的理解,当
thread_ptr
的对象,例如foo
,以std::ref(foo)
的方式传进线程函数之后,子线程是无法获取父线程设置的指针值的。也就是说,任何一个线程要使用foo
,都必须现在线程内设置foo
指向的对象才能使用。既然如此,为什么不在每个线程里分别使用
std::unique_ptr
呢?使用thread_ptr
的好处在哪里?