目录
1.线程池
1.1 线程池:
1.2 线程池的应用场景:
1.3 线程池的种类:
1.4 线程池示例:
1.5 线程池编程模拟实现:
2. 线程安全的单例模式
2.1 什么是单例模式
2.2 什么是设计模式
2.3 单例模式的特点
2.3.1 饿汉实现方式和懒汉实现方式
2.3.2 饿汉方式实现单例模式
2.3.3 懒汉方式实现单例模式
2.3.4 懒汉方式实现单例模式(线程安全版本)
3.STL、智能指针、线程安全
3.1 STL中的容器是否是线程安全的?
3.2 智能指针是否是线程安全的?
4. 其他常见的各种锁
5. 读者写者问题
5.1 读写锁
5.2 读写锁接口
5.3 编程模拟实现读写锁案例:
后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
——By 作者:新晓·故知
/* 1.1 线程池:
* 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
* 1.2 线程池的应用场景:
* 1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
* 2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
* 3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
* 1.3 线程池的种类:
* 1.4 线程池示例:
* 1. 创建固定数量线程池,循环从任务队列中获取任务对象,
* 2. 获取到任务对象后,执行任务对象中的任务接口
*/
1.5 线程池编程模拟实现:
version1:
shell脚本:
ps -aL | grep -E 'master|follower'
ThreadPool.hpp:
![]()
#pragma once #include
#include #include #include #include #include #include #include #include "Log.hpp" #include "Lock.hpp" using namespace std;int gThreadNum = 5; template class ThreadPool { private:ThreadPool(int threadNum = gThreadNum):threadNum_(threadNum),isStart_(false){assert(threadNum_ > 0);pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}ThreadPool(const ThreadPool &) = delete;void operator=(const ThreadPool &) = delete;public:static ThreadPool *getInstance(){static Mutex mutex;if (nullptr == instance) //仅仅是过滤重复的判断{LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁if (nullptr == instance){instance = new ThreadPool ();}}return instance;}//类内成员, 成员函数,都有默认参数this//static函数无法访问类内成员,只能通过接口,这里通过传入的this指针static void *threadRoutine(void *args){pthread_detach(pthread_self());ThreadPool *tp = static_cast *>(args);prctl(PR_SET_NAME, "follower");while (1){tp->lockQueue();while (!tp->haveTask()){tp->waitForTask();}//这个任务就被拿到了线程的上下文中T t = tp->pop();tp->unlockQueue();// for debugint one, two;char oper;t.get(&one, &two, &oper);//规定,所有的任务都必须有一个run方法Log() << "新线程完成计算任务: " << one << oper << two << "=" << t.run() << "\n";}}void start(){assert(!isStart_);for (int i = 0; i < threadNum_; i++){pthread_t temp;pthread_create(&temp, nullptr, threadRoutine, this);}isStart_ = true;}void push(const T &in){lockQueue();taskQueue_.push(in);choiceThreadForHandler();unlockQueue();}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}private:void lockQueue() { pthread_mutex_lock(&mutex_); }void unlockQueue() { pthread_mutex_unlock(&mutex_); }bool haveTask() { return !taskQueue_.empty(); }void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }void choiceThreadForHandler() { pthread_cond_signal(&cond_); }T pop(){T temp = taskQueue_.front();taskQueue_.pop();return temp;}private:bool isStart_;int threadNum_;queue taskQueue_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool *instance;// const static int a = 100; };template ThreadPool *ThreadPool ::instance = nullptr; ThreadPoolTest.cc:
![]()
#include "ThreadPool.hpp" #include "Task.hpp" #include
#include // 如何对一个线程进行封装, 线程需要一个回调函数(支持lambda)自行实现 // class tread{ // };int main() {prctl(PR_SET_NAME, "master");const string operators = "+/*/%";// unique_ptr > tp(new ThreadPool ());unique_ptr > tp(ThreadPool ::getInstance());tp->start();srand((unsigned long)time(nullptr) ^ getpid() ^ pthread_self());// 派发任务的线程while(true){int one = rand()%50;int two = rand()%10;char oper = operators[rand()%operators.size()];Log() << "主线程派发计算任务: " << one << oper << two << "=?" << "\n";Task t(one, two, oper);tp->push(t);sleep(1);} } Lock.hpp:
#pragma once #include
#include class Mutex { public:Mutex(){pthread_mutex_init(&lock_, nullptr);}void lock(){pthread_mutex_lock(&lock_);}void unlock(){pthread_mutex_unlock(&lock_);}~Mutex(){pthread_mutex_destroy(&lock_);}private:pthread_mutex_t lock_; };class LockGuard { public:LockGuard(Mutex *mutex) : mutex_(mutex){mutex_->lock();std::cout << "加锁成功..." << std::endl;}~LockGuard(){mutex_->unlock();std::cout << "解锁成功...." << std::endl;}private:Mutex *mutex_; }; Task.hpp:
#pragma once #include
#include class Task { public:Task() : elemOne_(0), elemTwo_(0), operator_('0'){}Task(int one, int two, char op) : elemOne_(one), elemTwo_(two), operator_(op){}int operator() (){return run();}int run(){int result = 0;switch (operator_){case '+':result = elemOne_ + elemTwo_;break;case '-':result = elemOne_ - elemTwo_;break;case '*':result = elemOne_ * elemTwo_;break;case '/':{if (elemTwo_ == 0){std::cout << "div zero, abort" << std::endl;result = -1;}else{result = elemOne_ / elemTwo_;}}break;case '%':{if (elemTwo_ == 0){std::cout << "mod zero, abort" << std::endl;result = -1;}else{result = elemOne_ % elemTwo_;}}break;default:std::cout << "非法操作: " << operator_ << std::endl;break;}return result;}int get(int *e1, int *e2, char *op){*e1 = elemOne_;*e2 = elemTwo_;*op = operator_;} private:int elemOne_;int elemTwo_;char operator_; }; Log.hpp:
![]()
#pragma once #include
#include #include std::ostream &Log() {std::cout << "Fot Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | " << " Thread[" << pthread_self() << "] | ";return std::cout; } makefile:
![]()
CC=g++ FLAGS=-std=c++11 LD=-lpthread bin=threadpool src=ThreadPoolTest.cc$(bin):$(src)$(CC) -o $@ $^ $(LD) $(FLAGS) .PHONY:clean clean:rm -f $(bin)
version 2:
![]()
![]()
g++ threadpool.cpp -o threadpool -pthread -lrt -std=c++0x
/*threadpool.h*/ /* 线程池:* 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。* 线程池的应用场景:* 1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。* 2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。* 3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.* 线程池的种类:* 线程池示例:* 1. 创建固定数量线程池,循环从任务队列中获取任务对象,* 2. 获取到任务对象后,执行任务对象中的任务接口*/ /*threadpool.hpp*/#ifndef __M_TP_H__ #define __M_TP_H__ #include
#include #include #include #define MAX_THREAD 5 typedef bool (*handler_t)(int); class ThreadTask { private:int _data;handler_t _handler;public:ThreadTask() : _data(-1), _handler(NULL) {}ThreadTask(int data, handler_t handler){_data = data;_handler = handler;}void SetTask(int data, handler_t handler){_data = data;_handler = handler;}void Run(){_handler(_data);} }; class ThreadPool { private:int _thread_max;int _thread_cur;bool _tp_quit;std::queue _task_queue;pthread_mutex_t _lock;pthread_cond_t _cond;private:void LockQueue(){pthread_mutex_lock(&_lock);}void UnLockQueue(){pthread_mutex_unlock(&_lock);}void WakeUpOne(){pthread_cond_signal(&_cond);}void WakeUpAll(){pthread_cond_broadcast(&_cond);}void ThreadQuit(){_thread_cur--;UnLockQueue();pthread_exit(NULL);}void ThreadWait(){if(_tp_quit){ThreadQuit();}pthread_cond_wait(&_cond, &_lock);}bool IsEmpty(){return _task_queue.empty();}static void *thr_start(void *arg){ThreadPool *tp = (ThreadPool *)arg;while (1){tp->LockQueue();while (tp->IsEmpty()){tp->ThreadWait();}ThreadTask *tt;tp->PopTask(&tt);tp->UnLockQueue();tt->Run();delete tt;}return NULL;}public:ThreadPool(int max = MAX_THREAD) : _thread_max(max), _thread_cur(max),_tp_quit(false){pthread_mutex_init(&_lock, NULL);pthread_cond_init(&_cond, NULL);}~ThreadPool(){pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}bool PoolInit(){pthread_t tid;for (int i = 0; i < _thread_max; i++){int ret = pthread_create(&tid, NULL, thr_start, this);if (ret != 0){std::cout << "create pool thread error\n";return false;}}return true;}bool PushTask(ThreadTask *tt){LockQueue();if (_tp_quit){UnLockQueue();return false;}_task_queue.push(tt);WakeUpOne();UnLockQueue();return true;}bool PopTask(ThreadTask **tt){*tt = _task_queue.front();_task_queue.pop();return true;}bool PoolQuit(){LockQueue();_tp_quit = true;UnLockQueue();while (_thread_cur > 0){WakeUpAll();usleep(1000);}return true;} }; #endif /*main.cpp*/ bool handler(int data) {srand(time(NULL));int n = rand() % 5;printf("Thread: %p Run Tast: %d--sleep %d sec\n", pthread_self(), data, n);sleep(n);return true; } int main() {int i;ThreadPool pool;pool.PoolInit();for (i = 0; i < 10; i++){ThreadTask *tt = new ThreadTask(i, handler);pool.PushTask(tt);}pool.PoolQuit();return 0; }
2.1 什么是单例模式
单例模式是一种 "经典的, 常用的, 常考的" 设计模式.2.2 什么是设计模式
IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式2.3 单例模式的特点
某些类, 只应该具有一个对象(实例), 就称之为单例. 例如一个男人只能有一个媳妇. 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.2.3.1 饿汉实现方式和懒汉实现方式
[洗完的例子] 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭. 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式. 懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.2.3.2 饿汉方式实现单例模式
只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.template
class Singleton {static T data;public:static T *GetInstance(){return &data;} }; 2.3.3 懒汉方式实现单例模式
存在一个严重的问题, 线程不安全. 第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例. 但是后续再次调用, 就没有问题了.template
class Singleton {static T *inst;public:static T *GetInstance(){if (inst == NULL){inst = new T();}return inst;} }; 2.3.4 懒汉方式实现单例模式(线程安全版本)
![]()
注意事项: 1. 加锁解锁的位置 2. 双重 if 判定, 避免不必要的锁竞争 3. volatile关键字防止过度优化
3.STL、智能指针、线程安全
3.1 STL中的容器是否是线程安全的?
不是. 原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响. 而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶). 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.3.2 智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题. 对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.4. 其他常见的各种锁
- 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
- 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。(乐观锁是由程序员实现的)
- CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
- 自旋锁,公平锁,非公平锁?
5. 读者写者问题
5.1 读写锁
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。 注意:写独占,读共享,读锁优先级高5.2 读写锁接口
设置读写优先
读写锁的行为 当前锁状态 读锁请求 写锁请求 无锁 可以
可以 读锁 可以 阻塞 写锁 阻塞 阻塞 ![]()
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref); /* pref 共有 3 种选择 PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况 PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁 */
初始化
销毁int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
加锁和解锁int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
5.3 编程模拟实现读写锁案例:
version 1:
#include
#include #include using namespace std;int board = 0; pthread_rwlock_t rw; void *reader(void* args) {const char *name = static_cast (args);cout << "run..." << endl;while(true){pthread_rwlock_rdlock(&rw);cout << "reader read : " << board << "tid: " << pthread_self() << endl;sleep(1);pthread_rwlock_unlock(&rw);} }void *writer(void *args) {const char *name = static_cast (args);sleep(1);while(true){pthread_rwlock_wrlock(&rw);board++;cout << "I am writer" << endl;sleep(1);pthread_rwlock_unlock(&rw);} }int main() {pthread_rwlock_init(&rw, nullptr);pthread_t r1,r2,r3,r4,r5,r6, w;pthread_create(&r1, nullptr, reader, (void*)"reader");pthread_create(&r2, nullptr, reader, (void*)"reader");pthread_create(&r3, nullptr, reader, (void*)"reader");pthread_create(&r4, nullptr, reader, (void*)"reader");pthread_create(&r5, nullptr, reader, (void*)"reader");pthread_create(&r6, nullptr, reader, (void*)"reader");pthread_create(&w, nullptr, writer, (void*)"writer");pthread_join(r1, nullptr);pthread_join(r2, nullptr);pthread_join(r3, nullptr);pthread_join(r4, nullptr);pthread_join(r5, nullptr);pthread_join(r6, nullptr);pthread_join(w, nullptr);pthread_rwlock_destroy(&rw);return 0; }
version 2:
![]()
#include
#include #include #include #include #include #include volatile int ticket = 1000; pthread_rwlock_t rwlock; void *reader(void *arg) {char *id = (char *)arg;while (1){pthread_rwlock_rdlock(&rwlock);if (ticket <= 0){pthread_rwlock_unlock(&rwlock);break;}printf("%s: %d\n", id, ticket);pthread_rwlock_unlock(&rwlock);usleep(1);}return nullptr; } void *writer(void *arg) {char *id = (char *)arg;while (1){pthread_rwlock_wrlock(&rwlock);if (ticket <= 0){pthread_rwlock_unlock(&rwlock);break;}printf("%s: %d\n", id, --ticket);pthread_rwlock_unlock(&rwlock);usleep(1);}return nullptr; } struct ThreadAttr {pthread_t tid;std::string id; }; std::string create_reader_id(std::size_t i) {// 利用 ostringstream 进行 string 拼接std::ostringstream oss("thread reader ", std::ios_base::ate);oss << i;return oss.str(); } std::string create_writer_id(std::size_t i) {// 利用 ostringstream 进行 string 拼接std::ostringstream oss("thread writer ", std::ios_base::ate);oss << i;return oss.str(); } void init_readers(std::vector &vec) {for (std::size_t i = 0; i < vec.size(); ++i){vec[i].id = create_reader_id(i);pthread_create(&vec[i].tid, nullptr, reader, (void *)vec[i].id.c_str());} } void init_writers(std::vector &vec) {for (std::size_t i = 0; i < vec.size(); ++i){vec[i].id = create_writer_id(i);pthread_create(&vec[i].tid, nullptr, writer, (void *)vec[i].id.c_str());} } void join_threads(std::vector const &vec) {// 我们按创建的 逆序 来进行线程的回收for (std::vector ::const_reverse_iterator it = vec.rbegin(); it != vec.rend(); ++it){pthread_t const &tid = it->tid;pthread_join(tid, nullptr);} } void init_rwlock() { #if 0 // 写优先pthread_rwlockattr_t attr;pthread_rwlockattr_init(&attr);pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);pthread_rwlock_init(&rwlock, &attr);pthread_rwlockattr_destroy(&attr); #else // 读优先,会造成写饥饿pthread_rwlock_init(&rwlock, nullptr); #endif } int main() {// 测试效果不明显的情况下,可以加大 reader_nr// 但也不能太大,超过一定阈值后系统就调度不了主线程了const std::size_t reader_nr = 1000;const std::size_t writer_nr = 2;std::vector readers(reader_nr);std::vector writers(writer_nr);init_rwlock();init_readers(readers);init_writers(writers);join_threads(writers);join_threads(readers);pthread_rwlock_destroy(&rwlock); } g++ -std=c++11 readwrite.cc -o readwrite -lpthread