1. CountDownLatch是什么
分析muduo::Thread时,看到Thread::start函数,如下所示,其作用为启动线程(关于muduo thread相关的内容,下次写个博客详细分析)
CountDownLatch latch_;
void Thread::start()
{
assert(!started_);
started_ = true;
// FIXME: move(func_)
detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_);
if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))
{
started_ = false;
delete data; // or no delete?
LOG_SYSFATAL << "Failed in pthread_create";
}
else
{
latch_.wait();
assert(tid_ > 0);
}
}
- 传入Thread内部变量 CountDownLatch latch_,调用pthread_create,创建线程函数startThread,入参为ThreadData* data,指向含有latch_的对象。
- startThread函数调用runInThread,其先获得tid(线程对应的实际进程的id),然后调用countDown,再执行实际的线程函数func_
- countDown中count–,再调用notifyAll-----pthread_cond_broadcast(&pcond_)
- 主线程中wait,调用的是pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()),等到cond,将线程从wait queue放到就绪队列,开始执行
CountDownLatch类定义
class CountDownLatch : noncopyable
{
public:
explicit CountDownLatch(int count);
void wait();
void countDown();
int getCount() const;
private:
mutable MutexLock mutex_;
Condition condition_ GUARDED_BY(mutex_);
int count_ GUARDED_BY(mutex_);
};
runInThread函数
void runInThread()
{
*tid_ = muduo::CurrentThread::tid();
tid_ = NULL;
latch_->countDown();
latch_ = NULL;
muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str();
::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName);
try
{
func_();
muduo::CurrentThread::t_threadName = "finished";
}
写到这里本来没毛病,但是我发现 Thread里写死了latch_(1),所以没有搞明白为什么要搞个count_,后来才知道CountDownLatch不止可以用在Thread里,还可以用在别的地方,比如主线程与多个子线程之间的同步。
- count = 3
- 主线程中wait
- 子线程countdown
2. CountDownLatch的使用
2.1 错误代码分析
为了实现上述线程同步,写了如下代码
#include "muduo/base/CountDownLatch.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Thread.h"
#include "muduo/base/Timestamp.h"
#include <boost/ptr_container/ptr_vector.hpp>
#include <unistd.h>
#include <algorithm>
#include <vector>
#include <stdio.h>
#include <memory>
#include <iostream>
using namespace muduo;
using namespace std;
class Test: public enable_shared_from_this<Test>
{
public:
Test(int threadNum);
void mainThFun();
void joinall();
private:
void threadFunc();
std::vector<std::shared_ptr<Thread>> threads_;
//boost::ptr_vector<Thread> threads_;
//std::vector<Thread*> threads_;
CountDownLatch latch_;
};
Test::Test(int threadNum):latch_(threadNum),threads_(threadNum)
{
for(int i = 0; i< threadNum ; i++)
{
threads_.emplace_back(new Thread(bind(&Test::threadFunc,shared_from_this())));
//threads_.push_back(new Thread(bind(&Test::threadFunc,this)));
}
//for_each(threads_.begin(),threads_.end(),[](std::unique_ptr<Thread> ut){ut->start();});
for_each(threads_.begin(),threads_.end(),bind(&Thread::start,placeholders::_1));
}
void Test::threadFunc()
{
sleep(2);
latch_.countDown();
cout<<"threadFunc is :"<<CurrentThread::tid()<<endl;
}
void Test::mainThFun()
{
latch_.wait();
cout<<"mainThFun : "<<CurrentThread::tid()<<endl;
}
void Test::joinall()
{
for_each(threads_.begin(),threads_.end(),bind(&Thread::join,placeholders::_1));
cout<<"joinall"<<endl;
}
int main()
{
//shared_ptr<Test> testcount = make_shared<Test>(3); 不能=指针
shared_ptr<Test> testcount(new Test(3));
//Test testcount(3);
cout<<"let's go!"<<CurrentThread::tid()<<endl;
testcount.mainThFun();
testcount.joinall();
return 0;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT(threaddemo)
include_directories(/data/lxz/build/release-install-cpp11/include)
link_directories(/data/lxz/build/release-install-cpp11/lib)
add_executable(thread_test Thread.cpp)
target_link_libraries(thread_test muduo_base pthread)
# add_test(NAME exception_test COMMAND exception_test)
一直运行出错,后来发现有两个问题
- 关于shared_from_this的使用,shared_from_this()不能用在构造函数里,因为其需要new 一个对象,但是这个对象构造是要先运行构造函数,所以存在先后问题,会导致出错
- 对于vector的初始化使用有误,threads_(threadNum),完成初始化后,再调用push_back,会在threadNum个元素之后插入(threadNum=3, 0-0-0-x),导致for_each使用时候使用了前三个nullptr调用start,出现段错误。
修改程序如下,使用vector.reserve,然后在成员函数doit中完成逻辑,但是依然很丑陋,可以使用ptr_vector进行优化
#include "muduo/base/CountDownLatch.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Thread.h"
#include "muduo/base/Timestamp.h"
#include <boost/ptr_container/ptr_vector.hpp>
#include <unistd.h>
#include <algorithm>
#include <vector>
#include <stdio.h>
#include <memory>
#include <iostream>
using namespace muduo;
using namespace std;
template<typename _InputIterator, typename _Function>
_Function
for_eacha(_InputIterator __first, _InputIterator __last, _Function __f)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
__glibcxx_requires_valid_range(__first, __last);
for (; __first != __last; ++__first)
{
__f(*__first);}
return __f; // N.B. [alg.foreach] says std::move(f) but it's redundant.
}
class Test: public enable_shared_from_this<Test>
{
public:
Test(int threadNum);
void mainThFun();
void joinall();
void doit();
private:
int num;
void threadFunc();
std::vector<std::shared_ptr<Thread>> threads_;
//boost::ptr_vector<Thread> threads_;
//std::vector<Thread*> threads_;
CountDownLatch latch_;
};
Test::Test(int threadNum):latch_(threadNum),num(threadNum)
{
threads_.reserve(num);
}
void Test::doit()
{
for(int i = 0; i< num ; i++)
{
threads_.emplace_back(new Thread(bind(&Test::threadFunc,shared_from_this()))); //这里不能使用,
//因为这是在构造函数,shared_ptr时,需要先调用构造函数
//threads_.push_back(new Thread(bind(&Test::threadFunc,this)));
//threads_.emplace_back(new Thread(bind(&Test::threadFunc,this)));
}
for_eacha(threads_.begin(),threads_.end(), [](shared_ptr<Thread> sh){ sh->start();});
}
void Test::threadFunc()
{
sleep(2);
latch_.countDown();
cout<<"threadFunc is :"<<CurrentThread::tid()<<endl;
}
void Test::mainThFun()
{
latch_.wait();
cout<<"mainThFun : "<<CurrentThread::tid()<<endl;
}
void Test::joinall()
{
for_each(threads_.begin(),threads_.end(),bind(&Thread::join,placeholders::_1));
cout<<"joinall"<<endl;
}
int main()
{
//shared_ptr<Test> testcount = make_shared<Test>(3); 不能=指针
shared_ptr<Test> testcount(new Test(3));
//Test testcount(3);
cout<<"let's go!"<<CurrentThread::tid()<<endl;
testcount->doit();
testcount->mainThFun();
testcount->joinall();
return 0;
}
运行成功,如下
let's go!4276
threadFunc is :4279
threadFunc is :4278
mainThFun : 4276
threadFunc is :4277
joinall
3. ptr_vector的使用
3.1 ptr_vector
ptr_(container)用于以异常安全的方式以最小的开销保存堆对象的容器
相比一般的例如vector< shared_ptr< T>>的优势
- 开销非常小
- 使用更方便
- 异常安全的指针存储和操作
- 比一般的智能指针的容器更快
- 可用于既不可分配也不可拷贝构造的类型
存储对象是不共享的,专有的
缺点是不如vector< shared_ptr< T>>等容器灵活
ptr_vector的源码如下
template
<
class T,
class CloneAllocator = heap_clone_allocator,
class Allocator = std::allocator<void*>
>
class ptr_vector : public
ptr_sequence_adapter< T,
std::vector<void*,Allocator>,
CloneAllocator >
{
typedef ptr_sequence_adapter< T,
std::vector<void*,Allocator>,
CloneAllocator >
base_class;
typedef ptr_vector<T,CloneAllocator,Allocator> this_type;
public:
BOOST_PTR_CONTAINER_DEFINE_SEQEUENCE_MEMBERS( ptr_vector,
base_class,
this_type )
explicit ptr_vector( size_type n,
const allocator_type& alloc = allocator_type() )
: base_class(alloc)
{
this->base().reserve( n );
}
};
//
// clonability
template< typename T, typename CA, typename A >
inline ptr_vector<T,CA,A>* new_clone( const ptr_vector<T,CA,A>& r )
{
return r.clone().release();
}
/
// swap
template< typename T, typename CA, typename A >
inline void swap( ptr_vector<T,CA,A>& l, ptr_vector<T,CA,A>& r )
{
l.swap(r);
}
}
ptr_vector的构造函数中,使用this->base().reserve( n ), base()为vector,即先vector.reserve(n),所以下面代码中
- boost::ptr_vector< Thread> threads_
- threads_(threadNum)
- threads_.push_back(new Thread(bind(&Test::threadFunc,this)));
- start
没有出现问题。
#include "muduo/base/CountDownLatch.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Thread.h"
#include "muduo/base/Timestamp.h"
#include <boost/ptr_container/ptr_vector.hpp>
#include <unistd.h>
#include <algorithm>
#include <vector>
#include <stdio.h>
#include <memory>
#include <iostream>
using namespace muduo;
using namespace std;
class Test
{
public:
Test(int threadNum);
void mainThFun();
void joinall();
private:
void threadFunc();
//std::vector<std::shared_ptr<Thread>> threads_;
boost::ptr_vector<Thread> threads_;
//std::vector<Thread*> threads_;
CountDownLatch latch_;
};
Test::Test(int threadNum):latch_(threadNum),threads_(threadNum)
{
for(int i = 0; i< threadNum ; i++)
{
//threads_.emplace_back(new Thread(bind(&Test::threadFunc,shared_from_this())));
threads_.push_back(new Thread(bind(&Test::threadFunc,this)));
}
//for_each(threads_.begin(),threads_.end(),[](std::unique_ptr<Thread> ut){ut->start();});
for_each(threads_.begin(),threads_.end(),bind(&Thread::start,placeholders::_1));
}
void Test::threadFunc()
{
sleep(2);
latch_.countDown();
cout<<"threadFunc is :"<<CurrentThread::tid()<<endl;
}
void Test::mainThFun()
{
latch_.wait();
cout<<"mainThFun : "<<CurrentThread::tid()<<endl;
}
void Test::joinall()
{
for_each(threads_.begin(),threads_.end(),bind(&Thread::join,placeholders::_1));
cout<<"joinall"<<endl;
}
int main()
{
//shared_ptr<Test> testcount = make_shared<Test>(3); 不能=指针
//shared_ptr<Test> testcount(new Test(3));
Test testcount(3);
cout<<"let's go!"<<CurrentThread::tid()<<endl;
testcount.mainThFun();
testcount.joinall();
return 0;
}
3.2 ptr_vector性能分析
借用https://www.cnblogs.com/my_life/articles/5452342.html的代码,增加了emplace_back以及make_shared,进行分析。
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <cstdio>
#include <ctime>
#include <stdint.h>
#include <memory>
class TSomeData
{
private:
int data;
public:
TSomeData(int d)
: data(d)
{
// Empty
}
};
const int TEST_ITERATIONS = 10000000;
typedef std::vector<std::shared_ptr<TSomeData> > TVectorOfShared;
typedef boost::ptr_vector<TSomeData> TPtrVector;
typedef std::vector<std::unique_ptr<TSomeData>> TVectorOfUnique;
int main()
{
clock_t start;
clock_t end;
start = ::clock();
TVectorOfShared vectorOfShared;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test vector of shared_ptr
std::shared_ptr<TSomeData> data(new TSomeData(i));
vectorOfShared.push_back(data);
}
end = ::clock();
printf("Vector of shared:\n Time executed: %u\n",
static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
start = ::clock();
TVectorOfShared vectorOfmakeShared;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test vector of shared_ptr
//boost::shared_ptr<TSomeData> data(new TSomeData(i));
vectorOfmakeShared.push_back(std::make_shared<TSomeData>(i));
}
end = ::clock();
printf("Vector of make_shared:\n Time executed: %u\n",
static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
start = ::clock();
TPtrVector ptrVector;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test ptr_vector
TSomeData* data = new TSomeData(i);
ptrVector.push_back(data);
}
end = ::clock();
printf("PtrVector:\n Time executed: %u\n",
static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
start = ::clock();
TVectorOfShared vectorOfSharedemp;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test vector of shared_ptr
//boost::shared_ptr<TSomeData> data(new TSomeData(i));
vectorOfSharedemp.emplace_back(new TSomeData(i));
}
end = ::clock();
printf("Vector of shared_emplace_back:\n Time executed: %u\n",
static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
start = ::clock();
TVectorOfUnique vectorOfUniqueemp;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test vector of shared_ptr
//boost::shared_ptr<TSomeData> data(new TSomeData(i));
vectorOfUniqueemp.emplace_back(new TSomeData(i));
}
end = ::clock();
printf("Vector of unique_emplace_back:\n Time executed: %u\n",
static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
start = ::clock();
TVectorOfUnique vectorOfUnique;
for (int i = 0; i < TEST_ITERATIONS; ++i) {
// Test vector of shared_ptr
std::unique_ptr<TSomeData> data(new TSomeData(i));
vectorOfUnique.push_back(move(data));
}
end = ::clock();
printf("Vector of unique_push:\n Time executed: %u\n",
static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
return 0;
}
在虚拟机上得出如下结果
lxz@lxz-VirtualBox:~/liuxz/testunp$ ./test
Vector of shared:
Time executed: 4083
Vector of make_shared:
Time executed: 5868
PtrVector:
Time executed: 1859
Vector of shared_emplace_back:
Time executed: 3486
Vector of unique_emplace_back:
Time executed: 5137
Vector of unique_push:
Time executed: 7154
- ptr_vector的大规模创建和使用时,效率高
- emplace_back相比push_back效率更高
- 具体使用哪个要看实际项目需求,另在使用boost的智能指针时比std的更快,具体没研究,不知道是不是错觉。