赞
踩
线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
1.需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
2.对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3.接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
线程池的种类:
线程池示例:
1.创建固定数量线程池,循环从任务队列中获取任务对象,
2.获取到任务对象后,执行任务对象中的任务接口
Thread.hpp
以下是自己封装实现的线程
#pragma once #include <iostream> #include <string> #include <functional> #include <cstring> #include <cassert> #include <pthread.h> namespace ThreadNs { typedef std::function<void *(void *)> func_t; const int num = 1024; class Thread { private: static void *start_routine(void *args) { Thread *td = static_cast<Thread *>(args); return td->callback(); } public: Thread() { char buffer[num]; snprintf(buffer, sizeof buffer, "thread-%d", threadnum++); _name = buffer; } void start(func_t func, void *args) { _func = func; _args = args; int n = pthread_create(&_tid, nullptr, start_routine, this); } void join() { int n = pthread_join(_tid, nullptr); assert(n == 0); (void)n; } std::string threadname() { return _name; } void *callback() { return _func(_args); } ~Thread() { } private: std::string _name; void *_args; func_t _func; pthread_t _tid; static int threadnum; }; int Thread::threadnum = 1; }
LockGuard.hpp
以下是自己封装实现的RAII风格的锁
#pragma once #include <cassert> #include <pthread.h> class Mutex { public: Mutex(pthread_mutex_t *lock_p = nullptr) : _lock_p(lock_p) { } void lock() { if (_lock_p) { int n = pthread_mutex_lock(_lock_p); assert(n == 0); (void)n; } } void unlock() { if (_lock_p) { int n = pthread_mutex_unlock(_lock_p); assert(n == 0); (void)n; } } ~Mutex() { } private: pthread_mutex_t *_lock_p; }; class LockGuard { public: LockGuard(pthread_mutex_t *mutex) : _mutex(mutex) { _mutex.lock(); } ~LockGuard() { _mutex.unlock(); } private: Mutex _mutex; };
Task.hpp
以下代码是用于任务的处理
#pragma once #include <iostream> #include <string> #include <functional> class Task { public: typedef std::function<int(int, int, char)> func_t; // using func_t = std::function<int(int, int, char)>; public: Task() { } Task(int x, int y, char op, func_t func) : _x(x), _y(y), _op(op), _callback(func) { } std::string operator()() { int result = _callback(_x, _y, _op); char buffer[1024]; snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result); return buffer; } std::string toTaskString() { char buffer[1024]; snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y); return buffer; } private: int _x; int _y; char _op; func_t _callback; }; const std::string oper = "+-*/%"; int calculate(int x, int y, char op) { int result = 0; switch (op) { case '+': result = x + y; break; case '-': result = x - y; break; case '*': result = x * y; break; case '/': { if (y == 0) { std::cerr << "div zero error" << std::endl; return -1; } else result = x / y; } break; case '%': { if (y == 0) { std::cerr << "mod zero error" << std::endl; return -1; } else result = x % y; } break; default: std::cerr << "请输入正确的操作符" << std::endl; break; } return result; }
ThreadPool.hpp
#pragma once #include "Thread.hpp" #include "LockGuard.hpp" using namespace ThreadNs; #include <vector> #include <queue> #include <iostream> const int gnum = 3; template <class T> class ThreadPool; template <class T> class ThreadData { public: ThreadData(ThreadPool<T> *tp, const std::string &threadname) : _threadpool(tp), _threadname(threadname) {} ~ThreadData() {} public: ThreadPool<T> *_threadpool; std::string _threadname; }; template <class T> class ThreadPool { private: static void* handleTask(void* args) { ThreadData<T>* td = static_cast<ThreadData<T>*>(args); while(true) { T t; { LockGuard lockguard(td->_threadpool->mutex()); while(td->_threadpool->isQueueEmpty()) { td->_threadpool->threadWait(); } t = td->_threadpool->pop(); } std::cout << td->_threadname << " 获取了一个任务: " << t.toTaskString() << " 并处理完成,结果是:" << t() << std::endl; } delete td; return nullptr; } public: bool isQueueEmpty() {return _task_queue.empty(); } void threadWait() { pthread_cond_wait(&_cond,&_mutex); } void lockQueue() {pthread_mutex_lock(&_mutex); } void unlockQueue() {pthread_mutex_unlock(&_mutex); } T pop() { T t = _task_queue.front(); _task_queue.pop(); return t; } pthread_mutex_t* mutex() { return &_mutex; } public: ThreadPool(const int &num = gnum) : _num(num) { pthread_mutex_init(&_mutex, nullptr); pthread_cond_init(&_cond, nullptr); for (int i = 0; i < _num; i++) { _threads.push_back(new Thread()); } } public: void run() { for (const auto &iter : _threads) { ThreadData<T> *td = new ThreadData<T>(this, iter->threadname()); iter->start(handleTask, td); std::cout << iter->threadname() << " start..." << std::endl; } } void push(T& in) { LockGuard lockguard(&_mutex); _task_queue.push(in); pthread_cond_signal(&_cond); } ~ThreadPool() { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); for (const auto &t : _threads) { delete t; } } private: int _num; std::vector<Thread *> _threads; std::queue<T> _task_queue; pthread_mutex_t _mutex; pthread_cond_t _cond; };
main.cc
#include "Task.hpp" #include "ThreadPool.hpp" #include <memory> #include <unistd.h> int main() { std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>()); tp->run(); int x, y; char op; while (1) { std::cout << "请输入数据1# "; std::cin >> x; std::cout << "请输入数据2# "; std::cin >> y; std::cout << "请输入你要进行的运算#"; std::cin >> op; Task t(x, y, op, calculate); tp->push(t); sleep(1); } return 0; }
IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式
某些类, 只应该具有一个对象(实例), 就称之为单例.例如一个男人只能有一个媳妇.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据
我们以洗碗的例子来说明懒汉模式和饿汉模式:
吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式。
懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度
饿汉方式实现单例模式
template <class T>
class Singleton
{
static T data;
public:
static T* GetInstance()
{
return &data;
}
};
只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例
懒汉方式实现单例模式
template <class T>
class Singleton
{
static T* inst;
public:
static T* GetInstance()
{
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
存在一个严重的问题, 线程不安全。第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例,但是后续再次调用, 就没有问题了
// 懒汉模式, 线程安全 template <class T> class Singleton { volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化. static std::mutex lock; public: static T* GetInstance() { if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能. lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new. if (inst == NULL) { inst = new T(); } lock.unlock(); } return inst; } };
注意事项:
1.加锁解锁的位置
2.双重 if 判定, 避免不必要的锁竞争
3.volatile关键字防止过度优化
STL中的容器是否是线程安全的?
不是。原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全。
智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
自旋锁,公平锁,非公平锁。
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
设置读写优先
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);
对于读者写者问题,我们了解一下即可,做实现的现象并不明显,理解其原理即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。