Linux_线程池

1、线程池

线程池是一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

应用场景

  • 需要大量的线程来完成任务,且完成任务的时间比较短。WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限出现错误。

线程池的实现

  • 创建固定数量线程池,循环从任务队列中获取任务对象。
  • 获取到任务对象后,执行任务对象中的任务接口。
#include<iostream>
#include<queue>
#include<pthread.h>
#include<unistd.h>
#include<time.h>

#define MAX_THREAD 10

typedef bool (*handler_t)(int);

using namespace std;

class ThreadTask
{
private:
	int _data;
	handler_t _handler;
public:
	ThreadTask()
		:_data(-1),_handler(nullptr)
	{}

	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;
	queue<ThreadTask *> _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(nullptr);
	}

	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 = static_cast<ThreadPool *>(arg);
		while(1)
		{
			tp->lockQueue();
			while(tp->isEmpty())
			{
				tp->threadWait();
			}
			ThreadTask * tt;
			tp->popTask(&tt);
			tp->unlockQueue();
			tt->run();
			delete tt;
		}
		return nullptr;
	}
public:
	ThreadPool(int max = MAX_THREAD)
		:_thread_max(max),
		_thread_cur(max),
		_tp_quit(false)
	{
		pthread_mutex_init(&_lock, nullptr);
		pthread_cond_init(&_cond, nullptr);
	}

	~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, nullptr, thr_start, this);
			if(ret != 0)
			{
				cout<<"create thread pool error..."<<endl;
				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();
			sleep(1);
		}
		return true;
	}
};

bool handler(int data)
{
	srand((unsigned int)time(nullptr));
	int n = rand() % 5;
	printf("thread:%p run task:%d, sleep %d sec\n", pthread_self(), data, n);
	sleep(n);
	return true;
}

int main()
{
	int i;
	ThreadPool pool;
	pool.poolInit();
	for(int i = 0; i < 30; ++i)
	{
		ThreadTask * tt = new ThreadTask(i, handler);
		pool.pushTask(tt);
	}
	pool.poolQuit();
	return 0;
}

在这里插入图片描述
2、线程安全的单例模式

单例模式是一种"经典的,常用的"设计模式。某些类只应该具有一个实例对象,称为单例。在很多服务器开发场景中,经常需要让服务器加载很多的数据到内存中,此时往往要用一个单例的类来管理这些数据。

懒汉方式核心思想是"延时加载",从而优化服务器的启动速度。饿汉模式核心思想是"启动时加载"。参见:单例模式

饿汉模式

template<class T>
class singleton
{
public:
	static T _data;
public:
	static T * getInstance()
	{
		return &_data;
	}
};

只要调用此类创建对象,则一个进程中只有一个对象的实例。

懒汉模式

template<class T>
class singleton
{
public:
	static T * _data;
public:
	static T * getInstance()
	{
		if(_data = nullptr)
		{
			_data = new T();
		}
		return _data;
	}
};

第一次调用getInstance()的时候,如果两个线程同时调用,可能会创建出两份对象的实例,造成线程不安全。

线程安全的懒汉模式

template<class T>
class singleton
{
public:
	volatile static T * _data;//volatile:防止编译器优化
	static std::mutex _lock;
public:
	static T * getInstance()
	{
		if(_data = nullptr)
		{
			_lock.lock();
			if(_data == nullptr)//双重判空,降低锁冲突效率,提高性能
			{
				_data = new T();
			}
			_lock.unlock();
		}
		return _data;
	}
};

注:双重判空,避免不必要的锁竞争;volatile关键字防止编译器优化

3、STL、智能指针与线程安全

STL容器不是线程安全的。STL的设计是为了将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。而且对于不同的容器,加锁方式的不同,性能可能也不同。因此STL默认不是线程安全的。如果需要在多线程环境下使用,需要调用者自行保证线程安全。

智能指针是线程安全的。对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效、原子的操作引用计数。

4、其他锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

发布了72 篇原创文章 · 获赞 25 · 访问量 7311

猜你喜欢

转载自blog.csdn.net/qq_41245381/article/details/104222114