【基本组件】线程与线程池

本节研究线程和线程池的C++实现;

线程线程库类型

(1)Linux线程库有两个流行的线程库,分别是LinuxThreads和NPTL,由于LinuxThreads的某些缺点,已经被NPTL取代,它们都是基于1:1模式实现,即1个用户线程被映射为1个内核线程;故每一个用户线程在内核中有唯一的标识;

(2)确定所使用的线程库,使用如下命令;

sykpour@sykpour:~/Desktop/Project/Project/build$ getconf GNU_LIBPTHREAD_VERSIONNPTL 2.11.1

线程标识

使用pthread_t存在问题

void* loop(void*){ cout << "Thread run" << endl; return NULL;}int main(){ pthread_t t1, t2; errno = pthread_create(&t1, NULL, loop, NULL); assert(!errno); pthread_join(t1, NULL); pthread_create(&t2, NULL, loop, NULL); assert(!errno); pthread_join(t2, NULL); cout << hex << t1 << " " << t2 << endl; return 0;}说明几点:

(1)验证该代码片段可得:上述t1和t2值相同;Pthreads只能保证同一进程中各线程pthread_t在同一时刻不同,不能保证同一线程具有不同的id;

(2)pthread_t在某些应用场合不能作为线程的标识,比如一个线程已经死亡,而此时创建一个新的线程,可能重用了旧的pthread_t,导致本来与旧线程交互的数据,开始又和新的线程错误执行;

(3)解决办法:由于NPTL基于1:1模式(1个用户线程被映射为1个内核线程)实现;故每一个用户线程在内核中也有唯一的标识,使用gettid()可以获得该标识,为了减少系统调用次数,可使用__thread进行缓存该id,这样只有第一次使用该id才会调用gettid()系统调用,但是可移植性会降低;下述代码中__thread缓存tid;syscall(SYS_gettid)为直接使用系统调用号执行系统调用,即直接获取某执行线程的唯一线程ID;实现代码如下:

namespace CurrentThread{pid_t gettid(){ return syscall(SYS_gettid);}__thread pid_t t_tid;static pthread_t tid(){ if (t_tid == 0){t_tid = gettid();} return t_tid;}}

ThreadThread声明

namespace CurrentThread{extern __thread pid_t t_tid;extern pid_t tid();}//notice: we should make Thread object's life longer than thread, so we shold always use fun: join,class Thread final{public: Thread(const Thread&) = delete; Thread& operator=(const Thread&) = delete; typedef std::function<void()> ThreadFunc; explicit Thread(const ThreadFunc& func); void start(); bool running() const {return _running; } int join() {int err = pthread_join(_threadId, NULL);return err; } pid_t tid() const {return _tid; }private: void _runInThread(); static void* _threadFunc(void*); pid_t _tid; pthread_t _threadId; ThreadFunc _func; bool _running;};

说明几点:

(1)_tid为线程的唯一标识,也就是利用上述CurrentThread和__thread来实现获得的;

(2)线程与Thread对象的生命周期,要注意Thread对象析构和线程终止的顺序关系;当调用thread.join()函数时,可以保证Thread对象在线程终止之后析构;若没有调用thread.join()函数,将会出现thread先析构的情况,此时C++析构会自动deatch线程,因此保证资源不会泄漏,但是线程在使用继续使用Thread相关资源时,执行过程将会出现未定义情况;因此我们应该保证使用thread.join()函数,即使Thread的生命周期要长于线程的生命周期;

Thread实现

__thread pid_t CurrentThread::t_tid = 0;pid_t CurrentThread::tid(){ if (t_tid == 0)t_tid = syscall(SYS_gettid); return t_tid;}Thread::Thread(const ThreadFunc& func):_func(func),_running(false){}void Thread::start(){ assert(!_running); _running = true; int err = pthread_create(&_threadId, NULL, Thread::_threadFunc, this); if (err != 0){LOG_SYSERR << "pthread_create system error";}}void Thread::_runInThread(){ assert(_running); try{_tid = CurrentThread::tid();//impotant, the time we obtain tid;LOG_INFO << "Thread tid[" << _tid << "] is threadFunc";if (_func)_func();} catch (…){LOG_SYSERR << "Exception happen in Thread";}}void* Thread::_threadFunc(void* obj)//remember obj's scope in self class{ Thread* thread = static_cast<Thread*>(obj); thread->_runInThread(); return NULL;}

说明几点:(注:所有与LOG_INFO类似的函数,将会在日至组件中介绍)

(1)static void* _threadFunc(void*)为Thread的static成员函数,而不是成员函数,因为pthread_create(&_threadId, NULL, Thread::_threadFunc, this)接收的函数类型必须是一个类似全局的函数,使用非static成员函数,显然我们还要传递相对应的对象指针,pthread_create是无法接收的;

(2)在_runInThread()中,使用catch (…)捕捉线程执行过程中的一切异常;

Thread测试

#include <Base/Thread.h>#include <stdio.h>using namespace std;using namespace Base;void func1() { printf("%s %d\n", "func1: tid", CurrentThread::tid());}void func2() { printf("%s %d\n", "func2: tid", CurrentThread::tid());}int main(void) { Thread thread1(func1); Thread thread2(func2);thread1.start(); thread2.start();thread1.join(); thread2.join(); return 0;}输出:

func1: tid 31463func2: tid 31464

线程池ThreadPool声明人生的成功不过是在紧要处多一份坚持,

【基本组件】线程与线程池

相关文章:

你感兴趣的文章:

标签云: