1 线程安全的单例模板类实现
静态 Public 成员函数 | |
---|---|
static T & | instance () 用于返回单例对象 |
静态 Private 成员函数 | |
---|---|
static void | init () 用于创建单例对象 |
static void | destroy () 用于销毁单例对象 |
静态 Private 属性 | |
---|---|
static pthread_once_t | ponce_ = PTHREAD_ONCE_INIT 能够保证一个函数只会被调用一次 |
static T * | value_ = NULL |
该类的文档由以下文件生成:
static T& instance()
{
pthread_once(&ponce_, &Singleton::init); // 保证只被调用一次并且可以保证线程安全,效率比锁更高
return *value_;
}
static void init()
{
value_ = new T();
::atexit(destroy);
}
atexit
注册一个函数,等程序结束后自动调用
由于是个单例模板类型,我们不知道究竟销毁的类型。而且单例已经私有化了他的析构函数,就不能再用智能指针去管理了,会很麻烦。
比如
class A;
A *pa ;
delete pa;
// 不会报错,只会警告的前向声明,他就是一个不完全的类型
static void destroy()
{
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; // 尽可能让他释放的时候,在编译阶段
// 因为sizeof是测了不完全类型,所以在sizeof的时候,就会报错 boost库很多这种写法
T_must_be_complete_type dummy; (void) dummy;
delete value_;
}
2 线程特定全局数据 TSD
pthread_key_create
可以指定回调函数销毁数据
pthread_key_delete
删除key
,不删除数据
pthread_getspecific
获取key
的数据,返回值是void *
类型
pthread_setspecific
指定特定的数据key
,令线程私有,但可以跨函数使用
muduo::ThreadLocal< T > 模板类 参考
类 muduo::ThreadLocal< T > 继承关系图:
[图例]
muduo::ThreadLocal< T > 的协作图:
[图例]
Public 成员函数 | |
---|---|
T & | value () |
静态 Private 成员函数 | |
---|---|
static void | destructor (void *x) |
Private 属性 | |
---|---|
pthread_key_t | pkey_ 线程特定数据 |
该类的文档由以下文件生成:
1 示例代码
#include <cstdio>
#include <cstring>
#include <pthread.h>
pthread_key_t key;
void func()
{
int *temp = static_cast<int *>(pthread_getspecific(key)); // 返回值是void* 需要强制转换
printf(" func pid = %d temp = %d %s\n",pthread_self(),*temp,__func__);
}
void* thread_func(void *arg)
{
pthread_setspecific(key,arg);
int* temp = static_cast<int *>(pthread_getspecific(key));
// 获取该线程的私有空间
printf("temp = %d pid = %d %s\n",*temp,pthread_self(),__func__);
*temp = (*temp)*1000;
func();
return NULL;
}
int main()
{
pthread_t t1,t2;
int a = 1,b = 2;
pthread_key_create(&key,NULL);
pthread_create(&t1,NULL,thread_func,&a);
pthread_create(&t2,NULL,thread_func,&b);
// 分别创建两个线程来测试各自的线程的全局变量
pthread_join(t1,NULL);
pthread_join(t2,NULL);
}
g++ test.cc -lpthread
./a.out
temp = 2 pid = -1437845760 thread_func
func pid = -1437845760 temp = 2000 func
temp = 1 pid = -1429453056 thread_func
func pid = -1429453056 temp = 1000 func
ThreadLocal()
{
pthread_key_create(&pkey_, &ThreadLocal::destructor); // 创建key和指定销毁的函数
}
~ThreadLocal()
{
pthread_key_delete(pkey_); // 只删除key,数据由构造函数注册的去销毁
}
根据线程的key
来获取,数据的引用
T& value()
{
T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_)); // 获取指定key对应的数据,并且转为T*
if (!perThreadValue) { // 如果线程key对应的数据是空的,那就说明还没创建这个数据
T* newObj = new T(); // 重新创建数据,并且设置
pthread_setspecific(pkey_, newObj);
perThreadValue = newObj;
}
return *perThreadValue;
}
2 示例代码
#include <muduo/base/ThreadLocal.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Thread.h>
#include <boost/noncopyable.hpp>
#include <stdio.h>
class Test : boost::noncopyable
{
public:
Test()
{
printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
}
~Test()
{
printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
}
const std::string &name() const { return name_; }
void setName(const std::string &n) { name_ = n; }
private:
std::string name_;
};
muduo::ThreadLocal<Test> testObj1;
muduo::ThreadLocal<Test> testObj2; // 定义了两个线程特定数据对象单例类,也就是每个线程的key都对应了一个test对象
void print()
{
printf("tid=%d, obj1 %p name=%s\n",
muduo::CurrentThread::tid(),
&testObj1.value(),
testObj1.value().name().c_str());
printf("tid=%d, obj2 %p name=%s\n",
muduo::CurrentThread::tid(),
&testObj2.value(),
testObj2.value().name().c_str());
}
void threadFunc()
{
print();
testObj1.value().setName("changed 1");
testObj2.value().setName("changed 42");
print();
}
int main()
{
testObj1.value().setName("main one"); // 获取线程特定数据,并且设置名字
print(); // 把tid和Test对象的地址,test对象名打印出来,test2根本没有设置Name所以打印出来的是空的
muduo::Thread t1(threadFunc);
t1.start();
t1.join();
testObj2.value().setName("main two");
print();
pthread_exit(0);
}
3 日志类
muduo::Logger类 参考
muduo::Logger 的协作图:
Impl
格式化日志,都是由该类实际实现的,借助LogStream
来输出日志LogStream
类实际上也并没有立刻输入到文件,而是先放到缓冲区,重载了<<
运算符FixedBuffer
缓冲区,是否输出到文件或者终端。等待flush
的时机。
类 | |
---|---|
class | Impl |
class | SourceFile |
Public 类型 | |
---|---|
enum | LogLevel { TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NUM_LOG_LEVELS } |
typedef void(* | OutputFunc) (const char *msg, int len) |
typedef void(* | FlushFunc) () |
Public 成员函数 | |
---|---|
Logger (SourceFile file, int line) | |
Logger (SourceFile file, int line, LogLevel level) | |
Logger (SourceFile file, int line, LogLevel level, const char *func) | |
Logger (SourceFile file, int line, bool toAbort) | |
LogStream & | stream () |
静态 Public 成员函数 | |
---|---|
static LogLevel | logLevel () |
static void | setLogLevel (LogLevel level) |
static void | setOutput (OutputFunc) |
static void | setFlush (FlushFunc) |
Private 属性 | |
---|---|
Impl | impl_ |
该类的文档由以下文件生成:
- Logging.h
- Logging.cc
1 日志类的宏定义
#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
// 如果日志级别大于TRACE 就不会构造TRACE
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
// 如果日志级别大于= DEBUG 就不会构造
// ……略
构造Logger
匿名对象,并且把文件名,行号,和日志级别,以及函数名传递进去
Logger::Logger(SourceFile file, int line, LogLevel level, const char* func)
: impl_(level, 0, file, line) // 调用impl构造函数
{
impl_.stream_ << func << ' '; // 格式化函数名称
}
// impl是Logger的内嵌类
impl
的构造函数会记录时间,流,等级和行号等
Public 成员函数 | |
---|---|
Impl (LogLevel level, int old_errno, const SourceFile &file, int line) | |
void | formatTime () 用于格式化时间 |
void | finish () 用于刷新缓冲区 |
Public 属性 | |
---|---|
Timestamp | time_ |
LogStream | stream_ |
LogLevel | level_ |
int | line_ |
SourceFile | basename_ |
Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
: time_(Timestamp::now()),
stream_(),
level_(level),
line_(line),
basename_(file)
{
formatTime();
CurrentThread::tid(); // 获取当前线程id的缓存
stream_ << T(CurrentThread::tidString(), 6); // 暂存线程名称
stream_ << T(LogLevelName[level], 6); // 暂存日志等级到缓冲区
if (savedErrno != 0) // 如果有了错误,就会输出
{
stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
}
}
20220105 01:48:22.854411Z 13505 TRACE main trace … - Log_test1.cc:8
格式化时间 线程ID 日志等级 函数名+行号
20220105 01:48:22.855115Z 13505 DEBUG main debug … - Log_test1.cc:9
20220105 01:48:22.855600Z 13505 INFO info … - Log_test1.cc:10
20220105 01:48:22.856487Z 13505 WARN warn … - Log_test1.cc:11
20220105 01:48:22.858032Z 13505 ERROR error … - Log_test1.cc:12
20220105 01:48:22.858525Z 13505 ERROR Permission denied (errno=13) syserr … - Log_test1.cc:15
20220105 01:48:22.859391Z 13505 FATAL Permission denied (errno=13) sysfatal … - Log_test1.cc:16
2 缓冲区详解
g_output
输出路径
Logger::OutputFunc g_output = defaultOutput;
Logger::FlushFunc g_flush = defaultFlush;
void defaultOutput(const char* msg, int len)
{
size_t n = fwrite(msg, 1, len, stdout);
//FIXME check n
(void)n;
}
void defaultFlush()
{
fflush(stdout);
}
默认是指定到stdout
标准输出,默认是刷新标准输出,可以通过更改输出函数,来改变输出位置
Logger::~Logger()
{
impl_.finish();
const LogStream::Buffer& buf(stream().buffer()); // 用的& buf 接收过来,并没有拷贝
g_output(buf.data(), buf.length());
if (impl_.level_ == FATAL)
{
g_flush();
abort();
}
}
3 示例代码
FILE *g_file;
void dummyOutput(const char *msg, int len)
{
if (g_file)
{
fwrite(msg, 1, len, g_file);
}
}
void dummyFlush()
{
fflush(g_file);
}
int main()
{
g_file = ::fopen("/chen/cpp/1.log", "ae");
Logger::setOutput(dummyOutput);
Logger::setFlush(dummyFlush);
LOG_TRACE << "trace ...";
LOG_DEBUG << "debug ...";
LOG_INFO << "info ...";
LOG_WARN << "warn ...";
LOG_ERROR << "error ...";
//LOG_FATAL<<"fatal ...";
errno = 13;
LOG_SYSERR << "syserr ...";
//LOG_SYSFATAL<<"sysfatal ...";
::fclose(g_file);
return 0;
}
4 LogStream输出流类
Public 类型 | |
---|---|
typedef detail::FixedBuffer< detail::kSmallBuffer > | Buffer |
Public 成员函数 | |
---|---|
self & | operator<< (bool v) |
self & | operator<< (short) |
self & | operator<< (unsigned short) |
self & | operator<< (int) |
self & | operator<< (unsigned int) |
self & | operator<< (long) |
self & | operator<< (unsigned long) |
self & | operator<< (long long) |
self & | operator<< (unsigned long long) |
self & | operator<< (const void *) |
self & | operator<< (float v) |
self & | operator<< (double) |
self & | operator<< (char v) |
self & | operator<< (const char *v) |
self & | operator<< (const string &v) |
self & | operator<< (const std::string &v) |
self & | operator<< (const StringPiece &v) |
void | append (const char *data, int len) |
const Buffer & | buffer () const |
void | resetBuffer () |
Private 类型 | |
---|---|
typedef LogStream | self |
Private 成员函数 | |
---|---|
void | staticCheck () |
template | |
void | formatInteger (T) |
Private 属性 | |
---|---|
Buffer | buffer_ |
静态 Private 属性 | |
---|---|
static const int | kMaxNumericSize = 32 |
格式化bool
类型
self& operator<<(bool v)
{
buffer_.append(v ? "1" : "0", 1);
return *this;
}
LogStream& LogStream::operator<<(short v)
{
*this << static_cast<int>(v); // 把short转为int再格式化
// 基本上涉及到整数的long unsigned long short都被转为int类型了
return *this;
}
5 FixedBuffer 缓冲区类
template<int SIZE> // SIZE为非类型参数,通过模板传递创建缓冲区大小
class FixedBuffer : boost::noncopyable
{// ……略};
Public 成员函数 | |
---|---|
void | append (const char *buf, size_t len) 往缓冲区追加数据 |
const char * | data () const |
int | length () const 当前有效长度 |
char * | current () 返回当前可用元素的位置 |
int | avail () const |
void | add (size_t len) |
void | reset () 空闲重置,重复利用 |
void | bzero () 清空为0,相当于memset |
const char * | debugString () |
void | setCookie (void(*cookie)()) |
string | asString () const |
Private 成员函数 | |
---|---|
const char * | end () const 指向最后一个元素的末尾 |
静态 Private 成员函数 | |
---|---|
static void | cookieStart () |
static void | cookieEnd () |
Private 属性 | |
---|---|
void(* | cookie_ )() |
char | data_ [SIZE] 存放缓冲区的数据 |
char * | cur_ 指向当前可有元素的位置 |
end()-cur_ =
当前可有的元素
cur_ - data =length =
当前有效长度
void append(const char* buf, size_t len)
{
if (implicit_cast<size_t>(avail()) > len) // implicit_cast 重新解释
{
memcpy(cur_, buf, len); // 如果当前可用空间大于len,就把数据拷贝过来
cur_ += len;
}
}
// 把数值类型,转换成字符串存储放到缓冲区中
void LogStream::formatInteger(T v)
{
if (buffer_.avail() >= kMaxNumericSize) // 缓冲区可用空间必须大于= 32位数,不然放不下
{
size_t len = convert(buffer_.current(), v);
buffer_.add(len);
}
}
const char digits[] = "9876543210123456789";
const char* zero = digits + 9;
template<typename T>
size_t convert(char buf[], T value)
{
T i = value;
char* p = buf;
do
{
int lsd = static_cast<int>(i % 10); // 最后一个数字取余10 ,这样子不停地取出最后一位数 123 321 出来
i /= 10;
*p++ = zero[lsd]; // 从0 偏移到3右边3的位置
} while (i != 0);
if (value < 0) // 如果是负数
{
*p++ = '-';
}
*p = '\0';
std::reverse(buf, p); // 反转字符串
return p - buf; // 求转换了多少个字符,比如1234 转了4个
}
会把指针转换成16进制的地址存放进去
6 其它 STL
提供通用的操作,而又不损失效率,通过定义一些结构体和类,利用模板特化给类型赋予特性
#ifdef HAVE_TYPE_TRAITS
// This makes vector<StringPiece> really fast for some STL implementations
template<> struct __type_traits<muduo::StringPiece> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
说通俗点就是,int类型可以用=赋值,无所谓,哪如果是struct和class呢,我需要在vector的时候对它进行拷贝,就会用到memset了,所以需要用到类型萃取。
struct TestType
{
bool Get()
{
return false;
}
};
,利用模板特化给类型赋予特性
#ifdef HAVE_TYPE_TRAITS
// This makes vector<StringPiece> really fast for some STL implementations
template<> struct __type_traits<muduo::StringPiece> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
说通俗点就是,int类型可以用=赋值,无所谓,哪如果是struct和class呢,我需要在vector的时候对它进行拷贝,就会用到memset了,所以需要用到类型萃取。
struct TestType
{
bool Get()
{
return false;
}
};