文章目录
1.再谈地址空间(引入线程)
1.1 地址空间
1.2 函数地址
1.3 引入线程
线程:在进程内部运行,是CPU调度的基本单位。
这么一个整体就是一个进程,这个进程里面有四个执行流,那么每个执行流被称为线程!
2.线程示例
2.1 实例演示
2.2 关于多线程和多进程的问题
1.已经有多进程了,为什么还要有多线程?
①进程创建的成本非常高(PCB、构建映射关系、加载代码和数据等等)
②线程创建的成本非常低(创建PCB,把进程的东西关联进来就可以!)
③线程调度成本也更低
④删除一个线程(删除PCB)要比删除一个进程(地址空间、页表一堆)轻松很多!当然线程也有缺点,因为所有的线程PID都一样,所以一个线程出异常就导致进程出异常,可能导致整个进程崩溃!而进程是有独立性的!所以线程和进程都要存在!!!!
2.为什么不同系统对于进程和线程的实现不一样?
linux使用进程模拟的线程 ,windows是真正的线程!
只适用不同的方法完成同样的目标罢了!只不过linux效率可能更高!
3.线程的特点
4.线程控制
线程控制分为创建、终止、等待、份力!!!
先来谈谈前面提到的pthread库是怎么回事?为啥编译非要加它?
5.线程的操作
其包括线程的创建、退出、多线程的创建、退出、分离、终止、取消等等!!!
for (auto tid : tids)
{
pthread_detach(tid); // 主线程分离新线程,新线程必须存在
}
6.线程id(tid)
pthread_self()可以获取线程的tid
std::string name = static_cast<const char *>(args);
while (true)
{
std::cout<<name<<"is running,tid:"<<pthread_self()<<std::endl;
sleep(1);
}
tid是给用户提供线程的id,不是内核中的lwp,而是自己维护的一个唯一值,tid是一个地址(线程控制块的地址)!
更准确点说是线程属性集合的起始虚拟地址----再pthread库中维护!
大致了解了tid我们再来看地址空间:
对于线程的局部存储,我们看下面的代码:
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <pthread.h>
// Linux有效, __thread只能修饰内置类型
__thread int gval = 100;
std::string ToHex(pthread_t tid)
{
char id[128];
snprintf(id, sizeof(id), "0x%lx", tid);
return id;
}
void *threadrun(void *args)
{
std::string name = static_cast<const char *>(args);
while (true)
{
std::string id = ToHex(pthread_self());
std::cout << name << " is running, tid: " << id << ", gval: " << gval << ", &gval: " << &gval << std::endl;
sleep(1);
gval++;
}
}
int main()
{
pthread_t tid; // 线程属性集合的起始虚拟地址--- 在pthread库中维护
pthread_create(&tid, nullptr, threadrun, (void *)"thread-1");
while (true)
{
std::cout << "main thread, gval: " << gval << ", &gval: " << &gval << std::endl;
sleep(1);
}
pthread_join(tid, nullptr);
return 0;
}
7.线程封装
我们在C++中的线程,以及别的语言的多线程,其本质都是对linux原生线程的封装。这里我们封装一个多线程来展示:
main.cpp:
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
using namespace ThreadMoudle;
void Print(const std::string &name)
{
int cnt = 1;
while (true)
{
std::cout << name << "is running, cnt: " << cnt++ << std::endl;
sleep(1);
}
}
const int gnum = 10;
int main()
{
// 我在管理原生线程, 先描述,在组织
// 构建线程对象
std::vector<Thread> threads;
for (int i = 0; i < gnum; i++)
{
std::string name = "thread-" + std::to_string(i + 1);
threads.emplace_back(name, Print);
sleep(1);
}
// 统一启动
for (auto &thread : threads)
{
thread.Start();
}
sleep(10);
// 统一结束
for (auto &thread : threads)
{
thread.Stop();
}
// 等待线程等待
for (auto &thread : threads)
{
thread.Join();
}
//单个线程的管理
// Thread t("thread-1", Print);
// t.Start();
// std::cout << t.Name() << ", status: " << t.Status() << std::endl;
// sleep(10);
// std::cout << t.Name() << ", status: " << t.Status() << std::endl;
// t.Stop();
// sleep(1);
// std::cout << t.Name() << ", status: " << t.Status() << std::endl;
// t.Join();
// std::cout << "join done" << std::endl;
return 0;
Thread.hpp:
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ThreadMoudle
{
// 线程要执行的方法,后面我们随时调整
typedef void (*func_t)(const std::string &name); // 函数指针类型
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running" << std::endl;
_isrunning = true;
_func(_name);
_isrunning = false;
}
public:
Thread(const std::string &name, func_t func):_name(name), _func(func)
{
std::cout << "create " << name << " done" << std::endl;
}
static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
{
Thread *self = static_cast<Thread*>(args); // 获得了当前对象
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
if(n != 0) return false;
return true;
}
std::string Status()
{
if(_isrunning) return "running";
else return "sleep";
}
void Stop()
{
if(_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop" << std::endl;
}
}
void Join()
{
if(!_isrunning)
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined" << std::endl;
}
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 线程要执行的回调函数
};
} // namespace ThreadModle
8.线程的互斥
8.1见见互斥(多线程并发执行)
多个线程能看到的资源叫做共享资源,我们需要对这部分资源进行保护!–可能会发生错误
先来看看线程多并发导致的错误:
8.2解决方案(加锁)
以上是全局锁的使用,如果我们要想把锁在函数种使用,该怎么办?
如下:
Thread.hpp:
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ThreadMoudle
{
class ThreadData
{
public:
ThreadData(const std::string &name, pthread_mutex_t *lock):_name(name), _lock(lock)
{}
public:
std::string _name;
pthread_mutex_t *_lock;
};
// 线程要执行的方法,后面我们随时调整
typedef void (*func_t)(ThreadData *td); // 函数指针类型
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running" << std::endl;
_isrunning = true;
_func(_td);
_isrunning = false;
}
public:
Thread(const std::string &name, func_t func, ThreadData *td):_name(name), _func(func), _td(td)
{
std::cout << "create " << name << " done" << std::endl;
}
static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
{
Thread *self = static_cast<Thread*>(args); // 获得了当前对象
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
if(n != 0) return false;
return true;
}
std::string Status()
{
if(_isrunning) return "running";
else return "sleep";
}
void Stop()
{
if(_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop" << std::endl;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined" << std::endl;
delete _td;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 线程要执行的回调函数
ThreadData *_td;
};
} // namespace ThreadModle
Main.cc:
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
using namespace ThreadMoudle;
int tickets = 10000; // 共享资源,造成了数据不一致的问题
void route(ThreadData *td)
{
while (true)
{
pthread_mutex_lock(td->_lock);
if (tickets > 0)
{
// 抢票过程
usleep(1000); // 1ms -> 抢票花费的时间
printf("who: %s, get a ticket: %d\n", td->_name.c_str(), tickets);
tickets--;
pthread_mutex_unlock(td->_lock);
}
else
{
pthread_mutex_unlock(td->_lock);
break;
}
}
}
static int threadnum = 4;
int main()
{
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, nullptr);
std::vector<Thread> threads;
for(int i = 0; i < threadnum; i++)
{
std::string name = "thread-" + std::to_string(i+1);
ThreadData *td = new ThreadData(name, &mutex);
threads.emplace_back(name, route, td);
}
for(auto &thread : threads)
{
thread.Start();
}
for(auto &thread : threads)
{
thread.Join();
}
pthread_mutex_destroy(&mutex);
// Thread t1("thread-1", route);
// Thread t2("thread-2", route);
// Thread t3("thread-3", route);
// Thread t4("thread-4", route);
// t1.Start();
// t2.Start();
// t3.Start();
// t4.Start();
// t1.Join();
// t2.Join();
// t3.Join();
// t4.Join();
}
8.3同步
虽然上面加锁解决了会将票数抢到负数的问题,但是,我们发现一旦某个线程开始抢票,那么这个线程就会一直抢,从而导致别的线程不能抢票。如下图所示
为了保证同步,也就是几个线程换着抢,就是每个线程都能轮流抢到一张,不会某个线程一直抢的情况,我们需要先认识一下条件变量:
下来我们写一个代码来使用上述接口:
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
const int num = 5;//创建5个线程
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;//全局锁
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;//全局条件变量
void *Wait(void *args)
{
std::string name = static_cast<const char *>(args);
while (true)
{
pthread_mutex_lock(&gmutex);//加锁
pthread_cond_wait(&gcond, &gmutex /*?*/); // 这里就是线程等待的位置,这句注释掉就会一直打印!
usleep(10000);//睡眠0.01s
std::cout << "I am : " << name << std::endl;
pthread_mutex_unlock(&gmutex);//解锁
// usleep(100000);
}
}
int main()
{
pthread_t threads[num];
for (int i = 0; i < num; i++)
{
char *name = new char[1024];
snprintf(name, 1024, "thread-%d", i + 1);
pthread_create(threads + i, nullptr, Wait, (void *)name);
usleep(10000);
}
sleep(1);
// 唤醒其他线程
while (true)
{
// pthread_cond_signal(&gcond);//唤醒一个线程
pthread_cond_broadcast(&gcond);//唤醒所有线程
//std::cout << "唤醒一个线程...." << std::endl;
std::cout << "唤醒全部线程...." << std::endl;
sleep(2);
}
for (int i = 0; i < num; i++)
{
pthread_join(threads[i], nullptr);//进程等待
}
return 0;
}
8.4生产消费模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
下来我们来实现一个阻塞队列:
BlockQueue.hpp:
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
const static int defaultcap = 5;//容量上限
template <typename T>
class BlockQueue//阻塞队列
{
private:
bool IsFull()//队列是否满了
{
return _block_queue.size() == _max_cap;
}
bool IsEmpty()//队列是否为空
{
return _block_queue.empty();
}
public:
BlockQueue(int cap = defaultcap) : _max_cap(cap)
{
pthread_mutex_init(&_mutex, nullptr);//初始化
pthread_cond_init(&_p_cond, nullptr);//对条件变量初始化
pthread_cond_init(&_c_cond, nullptr);
}
// 假设:2个消费者
void Pop(T *out)//把队列的数据带出去
{
pthread_mutex_lock(&_mutex);//加锁
while (IsEmpty()) // while可以保证代码的鲁棒性
{
//队列为空就等待,不能消费
pthread_cond_wait(&_c_cond, &_mutex); // 两个消费者都在这里等待了,一个消费者把数据拿走了(竞争成功),另一个消费者就在这里等待释放锁(只能继续往后走,但是没有数据了,但是已经绕过了IsEmpty条件,添加尚未满足,但是线程被异常唤醒的情况,叫做伪唤醒,所以不能用if判断,要用while!!!!)!
}
// 1. 没有空 || 2. 被唤醒了
*out = _block_queue.front();
_block_queue.pop();
// if(_block_queue.size() > hight_water)
// pthread_cond_signal(&_p_cond);
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_p_cond);//和生产者互相叫醒
}
// 一个生产者
void Equeue(const T &in)//把数据放入队列
{
pthread_mutex_lock(&_mutex);//加锁
while (IsFull()) // 判断队列满没满。if ?
{
// 满了,生产者不能生产,必须等待
// 可是在临界区里面(加锁和解锁之间)啊!!!pthread_cond_wait
// 被调用的时候:除了让自己继续排队等待,还会自己释放传入的锁
// 函数返回的时候,不就还在临界区了!(会自动释放锁)
// 返回时:必须先参与锁的竞争,重新加上锁,该函数才会返回!
pthread_cond_wait(&_p_cond, &_mutex);
}
// 1. 没有满 || 2. 被唤醒了
_block_queue.push(in); // 生产到阻塞队列
pthread_mutex_unlock(&_mutex);//解锁
// 让消费者消费,生产了一个数据赶紧通知消费者
pthread_cond_signal(&_c_cond); // pthread_cond_broadcast : 一种场景
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_p_cond);
pthread_cond_destroy(&_c_cond);
}
private:
std::queue<T> _block_queue; // 临界资源
int _max_cap;//最大容量
pthread_mutex_t _mutex;//定义一把锁
pthread_cond_t _p_cond; // 生产者条件变量
pthread_cond_t _c_cond; // 消费者条件变量
// int low_water = _max_cap/3 //更复杂的场景,可以尝试,如果容量小于1/3,那么就通知生产者赶紧生产
// int hight_water _max_cap/3*2//如果容量大于2/3,赶紧通知消费者赶紧消费!
};
main.cpp:
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <unistd.h>
void *Consumer(void *args)
{
BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);
while(true)//不断把任务拿走
{
// 1. 获取数据
task_t t;
bq->Pop(&t);
// 2. 处理数据
// t.Excute();
t();//使用仿函数
// std::cout << "Consumer -> " << t.result() << std::endl;
}
}
void *Productor(void *args)
{
srand(time(nullptr) ^ getpid());//设置随机数
BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);
while(true)
{
// 1. 构建数据/任务
// int x = rand() % 10 + 1; // [1, 10]
// usleep(x * 1000);
// int y = rand() % 10 + 1; // [1, 10]
// Task t(x, y);
// 2. 生产数据
bq->Equeue(Download);
std::cout << "Productor -> Download" << std::endl;
sleep(1);
}
}
int main()
{
BlockQueue<task_t> *bq = new BlockQueue<task_t>();//创建阻塞队列
pthread_t c1,c2, p1,p2,p3;
//多生产、多消费
pthread_create(&c1, nullptr, Consumer, bq);
pthread_create(&c2, nullptr, Consumer, bq);
pthread_create(&p1, nullptr, Productor, bq);
pthread_create(&p2, nullptr, Productor, bq);
pthread_create(&p3, nullptr, Productor, bq);
pthread_join(c1, nullptr);
pthread_join(c2, nullptr);
pthread_join(p1, nullptr);
pthread_join(p2, nullptr);
pthread_join(p3, nullptr);
return 0;
}
其也可以调用类等结果,当然也可以传递函数对象!!如下:
Task.hpp:
#pragma once
#include<iostream>
#include<functional>
// typedef std::function<void()> task_t;
using task_t = std::function<void()>;//与上面等价,定义一个函数类型!
void Download()
{
std::cout << "我是一个下载的任务" << std::endl;
}
// // 要做加法
// class Task
// {
// public:
// Task()
// {
// }
// Task(int x, int y) : _x(x), _y(y)
// {
// }
// void Excute()
// {
// _result = _x + _y;
// }
// void operator ()()//仿函数
// {
// Excute();
// }
// std::string debug()
// {
// std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";
// return msg;
// }
// std::string result()
// {
// std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
// return msg;
// }
// private:
// int _x;
// int _y;
// int _result;
// };
执行不同类型的任务,只需要切换 BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);中
<>里面的内容即可,例如<’Task>,<‘int>等。
9.环形队列&&信号量
9.1 信号量的接口
申请信号量的本质就是对公共资源的一种预定机制。
9.2 基于环形队列的生产消费模型
RingQueue.hpp:
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <semaphore.h>
template <typename T>
class RingQueue
{
private:
//P操作表示申请一个资源,V操作表示释放一个资源。
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t &s)
{
sem_post(&s);
}
public:
RingQueue(int max_cap)
: _ringqueue(max_cap), _max_cap(max_cap), _c_step(0), _p_step(0)//开始下标为0
{
sem_init(&_data_sem, 0, 0);
sem_init(&_space_sem, 0, max_cap);//初始化信号量
pthread_mutex_init(&_c_mutex, nullptr);//初始化锁
pthread_mutex_init(&_p_mutex, nullptr);
}
void Push(const T &in) //生产者
{
// 信号量:是一个计数器,是资源的预订机制。预订:在外部,可以不判断资源是否满足,就可以知道内部资源的情况!
P(_space_sem); // 信号量这里,对资源进行使用,申请,为什么不判断一下条件是否满足???信号量本身就是判断条件!
//先申请信号量,再加锁效率高!
pthread_mutex_lock(&_p_mutex); //环形队列多生产多消费不同于阻塞队列,其可以运行五个线程同时进来,但是下标只有一个,所以需要加锁
_ringqueue[_p_step] = in;
_p_step++;
_p_step %= _max_cap;
pthread_mutex_unlock(&_p_mutex);//解锁
V(_data_sem);
}
void Pop(T *out) // 消费
{
P(_data_sem);
pthread_mutex_lock(&_c_mutex); //加锁
*out = _ringqueue[_c_step];
_c_step++;
_c_step %= _max_cap;
pthread_mutex_unlock(&_c_mutex);//解锁
V(_space_sem);
}
~RingQueue()
{
sem_destroy(&_data_sem);
sem_destroy(&_space_sem);
pthread_mutex_destroy(&_c_mutex);
pthread_mutex_destroy(&_p_mutex);
}
private:
std::vector<T> _ringqueue;//维护的基本结构
int _max_cap;//最大容量
int _c_step;//消费者下标
int _p_step;//生产者下标
sem_t _data_sem; // 消费者关心(数据信号量)
sem_t _space_sem; // 生产者关心(空间信号量)
pthread_mutex_t _c_mutex;//生产者互斥锁
pthread_mutex_t _p_mutex;//消费者互斥锁
};
main.cpp:
#include "RingQueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
void *Consumer(void*args)
{
RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
while(true)
{
Task t;
// 1. 消费
rq->Pop(&t);
// 2. 处理数据
t();
std::cout << "Consumer-> " << t.result() << std::endl;
}
}
void *Productor(void*args)
{
RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
while(true)
{
sleep(1);
// 1. 构造数据
int x = rand() % 10 + 1; //[1, 10]
usleep(x*1000);
int y = rand() % 10 + 1;
Task t(x, y);
// 2. 生产
rq->Push(t);
std::cout << "Productor -> " << t.debug() << std::endl;
}
}
int main()
{
srand(time(nullptr) ^ getpid());
RingQueue<Task> *rq = new RingQueue<Task>(5);//对象搞出来
pthread_t c1, c2, p1, p2, p3;
pthread_create(&c1, nullptr, Consumer, rq);
pthread_create(&c2, nullptr, Consumer, rq);
pthread_create(&p1, nullptr, Productor, rq);
pthread_create(&p2, nullptr, Productor, rq);
pthread_create(&p3, nullptr, Productor, rq);
pthread_join(c1, nullptr);
pthread_join(c2, nullptr);
pthread_join(p1, nullptr);
pthread_join(p2, nullptr);
pthread_join(p3, nullptr);
return 0;
}
Task.hpp:
#pragma once
#include<iostream>
#include<functional>
// typedef std::function<void()> task_t;
// using task_t = std::function<void()>;
// void Download()
// {
// std::cout << "我是一个下载的任务" << std::endl;
// }
// 要做加法
class Task
{
public:
Task()
{
}
Task(int x, int y) : _x(x), _y(y)
{
}
void Excute()
{
_result = _x + _y;
}
void operator ()()
{
Excute();
}
std::string debug()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";
return msg;
}
std::string result()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
return msg;
}
private:
int _x;
int _y;
int _result;
};
10.线程池
线程池:
- 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
- 线程池的应用场景:
-
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
-
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
-
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
ThreadPool.hpp:
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include "Thread.hpp"
#include "Log.hpp"
using namespace ThreadMoudle;
using namespace log_ns;
static const int gdefaultnum = 5;//默认创建5个线程
void test()
{
while (true)
{
std::cout << "hello world" << std::endl;
sleep(1);
}
}
template <typename T>
class ThreadPool
{
private:
void LockQueue()
{
pthread_mutex_lock(&_mutex);
}
void UnlockQueue()
{
pthread_mutex_unlock(&_mutex);
}
void Wakeup()
{
pthread_cond_signal(&_cond);
}
void WakeupAll()
{
pthread_cond_broadcast(&_cond);
}
void Sleep()
{
pthread_cond_wait(&_cond, &_mutex);
}
bool IsEmpty()
{
return _task_queue.empty();
}
void HandlerTask(const std::string &name) // this处理任务
{
while (true)
{
// 取任务
LockQueue();
while (IsEmpty() && _isrunning)//没有任务且线程池没退出
{
_sleep_thread_num++;
LOG(INFO, "%s thread sleep begin!\n", name.c_str());
Sleep();
LOG(INFO, "%s thread wakeup!\n", name.c_str());
_sleep_thread_num--;
}
// 判定一种情况
if (IsEmpty() && !_isrunning)
{
//std::cout << name << " 线程池quit" << std::endl;
UnlockQueue();
LOG(INFO, "%s thread quit\n", name.c_str());
break;
}
// 有任务
T t = _task_queue.front();
_task_queue.pop();
UnlockQueue();
// 处理任务
t(); // 处理任务,此处不用/不能在临界区中处理
// std::cout << name << ": " << t.result() << std::endl;
LOG(DEBUG, "hander task done, task is : %s\n", t.result().c_str());
}
}
public:
ThreadPool(int thread_num = gdefaultnum) : _thread_num(thread_num), _isrunning(false), _sleep_thread_num(0)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
void Init()
{
func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);//将HandlerTask和this指针强关联起来,构建了一个新的方法,就可以直接调用func
for (int i = 0; i < _thread_num; i++)//构建线程对象
{
std::string threadname = "thread-" + std::to_string(i + 1);
_threads.emplace_back(threadname, func);
LOG(DEBUG, "construct thread %s done, init success\n", threadname.c_str());
}
}
void Start()
{
_isrunning = true;//线程池启动
for (auto &thread : _threads)
{
LOG(DEBUG, "start thread %s done.\n", thread.Name().c_str());
thread.Start();
}
}
void Stop()
{
LockQueue();
_isrunning = false;
WakeupAll();
UnlockQueue();
LOG(INFO, "Thread Pool Stop Success!\n");
}
void Equeue(const T &in)//像线程池推送任务
{
LockQueue();
if (_isrunning)
{
_task_queue.push(in);
if (_sleep_thread_num > 0)
Wakeup();
}
UnlockQueue();
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
private:
int _thread_num;//线程数量
std::vector<Thread> _threads;
std::queue<T> _task_queue;
bool _isrunning;
int _sleep_thread_num;//计数器(休眠线程个数)
pthread_mutex_t _mutex;//互斥锁
pthread_cond_t _cond;//条件变量
};
main.cpp:
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Log.hpp"
// #include <memory>
using namespace log_ns;
int main()
{
EnableScreen();
// std::unique_ptr<ThreadPool> tp = std::make_unique<ThreadPool>(); //c++14
// ThreadPool<Task> *tp = new ThreadPool<Task>();
// tp->Init();
// tp->Start();
int cnt = 10;
while(cnt)
{
// 不断地向线程池推送任务
sleep(1);
Task t(1,1);
ThreadPool<Task>::GetInstance()->Equeue(t);
LOG(INFO, "equeue a task, %s\n", t.debug().c_str());
sleep(1);
cnt--;
}
ThreadPool<Task>::GetInstance()->Stop();
LOG(INFO, "thread pool stop!\n");
return 0;
}
Thread.hpp:
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
namespace ThreadMoudle
{
// 线程要执行的方法,后面我们随时调整
// typedef void (*func_t)(ThreadData *td); // 函数指针类型
// typedef std::function<void()> func_t;
using func_t = std::function<void(const std::string&)>;//相当于上面的typedef,对std::function<void()>这个类型重命名为func_t
class Thread
{
public:
void Excute()
{
_isrunning = true;
_func(_name);//回调
_isrunning = false;
}
public:
Thread(const std::string &name, func_t func):_name(name), _func(func)
{
}
static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
{
Thread *self = static_cast<Thread*>(args); // 获得了当前对象
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
if(n != 0) return false;
return true;
}
std::string Status()
{
if(_isrunning) return "running";
else return "sleep";
}
void Stop()
{
if(_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 线程要执行的回调函数
};
} // namespace ThreadModle
Task.hpp:
#pragma once
#include<iostream>
#include<functional>
// typedef std::function<void()> task_t;
// using task_t = std::function<void()>;
// void Download()
// {
// std::cout << "我是一个下载的任务" << std::endl;
// }
// 要做加法
class Task
{
public:
Task()
{
}
Task(int x, int y) : _x(x), _y(y)
{
}
void Excute()
{
_result = _x + _y;
}
void operator ()()
{
Excute();
}
std::string debug()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";
return msg;
}
std::string result()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
return msg;
}
private:
int _x;
int _y;
int _result;
};
线程池写好之后我们想要给他加日志:
Log.hpp:
#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"
namespace log_ns
{
enum//日志等级
{
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LevelToString(int level)//日志等级转化为字符串描述
{
switch (level)
{
case DEBUG:
return "DEBUG";
case INFO:
return "INFO";
case WARNING:
return "WARNING";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetCurrTime()//获取当前时间
{
time_t now = time(nullptr);
struct tm *curr_time = localtime(&now);//通过时间戳转化为年月日时分秒
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
curr_time->tm_year + 1900,//注意year要加1900是一个函数约定
curr_time->tm_mon + 1,//月份也一样
curr_time->tm_mday,
curr_time->tm_hour,
curr_time->tm_min,
curr_time->tm_sec);
return buffer;
}
class logmessage
{
public:
std::string _level;//这一条日志
pid_t _id;//日志的pid
std::string _filename;//文件名
int _filenumber;//文件行号
std::string _curr_time;//时间(年月日时分秒)
std::string _message_info;//日志内容
};
#define SCREEN_TYPE 1
#define FILE_TYPE 2
const std::string glogfile = "./log.txt";
pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;
// log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );
class Log
{
public:
Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
{
}
void Enable(int type)
{
_type = type;
}
void FlushLogToScreen(const logmessage &lg)//写到屏幕上
{
printf("[%s][%d][%s][%d][%s] %s",
lg._level.c_str(),
lg._id,
lg._filename.c_str(),
lg._filenumber,
lg._curr_time.c_str(),
lg._message_info.c_str());
}
void FlushLogToFile(const logmessage &lg)//写到文件中
{
std::ofstream out(_logfile, std::ios::app);//追加,不覆盖
if (!out.is_open())
return;
char logtxt[2048];
snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
lg._level.c_str(),
lg._id,
lg._filename.c_str(),
lg._filenumber,
lg._curr_time.c_str(),
lg._message_info.c_str());
out.write(logtxt, strlen(logtxt));
out.close();
}
void FlushLog(const logmessage &lg)
{
// 加过滤逻辑 --- TODO
LockGuard lockguard(&glock);
switch (_type)
{
case SCREEN_TYPE:
FlushLogToScreen(lg);
break;
case FILE_TYPE:
FlushLogToFile(lg);
break;
}
}
void logMessage(std::string filename, int filenumber, int level, const char *format, ...)//...是可变参数
{
logmessage lg;
lg._level = LevelToString(level);
lg._id = getpid();
lg._filename = filename;
lg._filenumber = filenumber;
lg._curr_time = GetCurrTime();
//获取可变参数↓
va_list ap;
va_start(ap, format);
char log_info[1024];
vsnprintf(log_info, sizeof(log_info), format, ap);
va_end(ap);
lg._message_info = log_info;
// 打印出来日志
FlushLog(lg);
}
~Log()
{
}
private:
int _type;
std::string _logfile;
};
Log lg;
//宏实现可变参数
#define LOG(Level, Format, ...) \
do \
{ \
lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
} while (0)
#define EnableScreen() \
do \
{ \
lg.Enable(SCREEN_TYPE); \
} while (0)
#define EnableFILE() \
do \
{ \
lg.Enable(FILE_TYPE); \
} while (0)
};
LockGuard.hpp :
#pragma once
#include <pthread.h>
class LockGuard
{
public:
LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t *_mutex;
};
11.单例模式
12.可重入与线程安全、死锁
线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。