Liunx线程安全
目录
一、线程互斥
1、进程线程间的互斥相关背景概念
2、互斥量
(1)互斥量的引出
(2)互斥量的接口
(3)实现锁的方式
(4)锁的封装
编辑
二、同步
1、条件变量
(1)概念
(2)同步概念和竞态条件
(3)条件变量接口
(4)代码演示
三、生产消费者模型
1、概念
2、总结
3、代码实现
4、条件变量的封装
四、信号量
1、信号量的引入
2、信号量接口
3、生产消费模型场景(基于环形队列)
代码实现
五、日志书写
六、线程池
七、线程安全的单例模式
1、单例模式的概念
2、实现方式
(1)饿汉实现方式
(2)懒汉实现方式
八、线程安全和重入问题
1、概念
2、结论
九、死锁问题
1、概念
2、死锁产生的必须条件
一、线程互斥
1、进程线程间的互斥相关背景概念
- 临界资源:多线程执行流被保护的共享的资源叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界区资源,通常临界资源起保护作用
- 原子性:不会被任何调度机制打断的操作,该操作只有完成和未完成
2、互斥量
(1)互斥量的引出
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获取这种变量
- 但有的时候,很多变量都需要在线程间共享,这种的变量叫做共享变量,可以通过数据的共享,完成线程之间的交互
- 多个线程并发的操作共享变量会带来一些问题
如下图的抢票逻辑

观察上方的代码我们发现虽然我们使用判断使ticket不小于0,为什么结果还是有负数的地方

- 我们假设有两个线程,其中一个线程进入判断的时候ticket为正数,进入if判断时被切换走了,带走了线程上下文,等到ticket为零的时候才切换回来这时候,该线程还是从进入if判断的时候开始走,当ticket-- 的时候ticket 为负数
ticket--是安全的吗

汇编的时候ticket--会变成三条语句,随时都可能被切换
其实要解决上述的抢票系统,需要三点
- 代码必须有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
- 如果多个线程同时要求执行临界区的代码,并且此时临界区没有线程在执行,那么只能允许一个线程进入该临界区。
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到上述的三点,本质需要一把锁,Linux提供这把锁的就叫互斥量
(2)互斥量的接口
初始化互斥量
方法1
静态分配(锁是全局的)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
动态分配(锁在栈上开批)
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
上锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
使用

gmutex自己就是全局变量,他如何保护自己是安全的
gmutex 是原子的
所有线程都要申请锁吗
所有线程都必须申请锁,申请锁成功线程会继续向后运行,申请失败线程会阻塞
如果进入临界区,线程会被切换吗
会被切换,不会导致并发问题,因为你是持有锁被切换的,其他的线程进不来临界区


(3)实现锁的方式
硬件实现方案
关闭中断
软件实现方案
一条汇编语句
swap 或 exchange 指令,该指令的作用是把寄存器和内存单元的数据相交换

以上的代码,如果线程被切换走了,mutex的值依旧是0,判断失败不会更改数值
(4)锁的封装
二、同步
1、条件变量
(1)概念
当一个线程互斥地访问某个变量的时候,它可能发现在其他线程改变状态之前,它什么也做不了,为了提升效率,这种情况我们就需要使用条件变量
例子:

如上图一个人负责放苹果一个人负责拿苹果,不过那还是放都必须要申请锁,但是如果一个人的竞争力如果太强可能会造成,只有一个人一直在拿或者一直在放,条件变量就是每当放苹果的时候,唤醒拿苹果的让他拿,这就是条件变量
(2)同步概念和竞态条件
- 同步:在保证数据安全的情况下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
- 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件
- 首先需要明确的是,单纯的加锁是会存在某些问题的,如果个别线程的竞争力特别强,每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,这就可能导致其他线程长时间竞争不到锁,引起饥饿问题。
- 单纯的加锁是没有错的,它能够保证在同一时间只有一个线程进入临界区,但它没有高效的让每一个线程使用这份临界资源。
- 现在我们增加一个规则,当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后。
- 增加这个规则之后,下一个获取到锁的资源的线程就一定是在资源等待队列首部的线程。
(3)条件变量接口
条件变量主要包括两个动作:
- 一个线程等待条件变量的条件成立而被挂起。
- 另一个线程使条件成立后唤醒等待的线程。
初始化
静态分配
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态分配
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:cond:要初始化的条件变量
attr:NULL
销毁
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:cond:要在这个条件变量上等待
mutex:互斥量,后⾯详细解释
唤醒线程
int pthread_cond_broadcast(pthread_cond_t *cond); 唤醒所有线程
int pthread_cond_signal(pthread_cond_t *cond); 唤醒一个线程
(4)代码演示

三、生产消费者模型
1、概念

为什么要有超市,为什么要有超市这种模式
- 减少生产和消费过程中产生的成本
- 支持生产和消费的忙闲不均
- 维护松耦合关系
如何正确的进行生产和消费
本质:就是维护生产者和消费者之间的关系
生产者之间的关系
互斥关系
消费者之间的关系
互斥关系
生产和消费者之间的关系
互斥和同步
2、总结
三种关系,两个角色,一个交易场所(通常是某种数据结构对象)
3、代码实现
基于BlockingQueue的生产消费者模型
BlockQueue.hpp
#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__
#include<string>
#include<iostream>
#include<pthread.h>
#include <queue>
#include <cstdint>
#include <unistd.h>
const uint32_t grep = 5;
template<typename T>
class BlockQueue
{
public:BlockQueue(uint32_t cap = grep):_cap(cap),_productor_wait_num(0),_consumer_wait_num(0){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_product_cond, nullptr);pthread_cond_init(&_consum_cond, nullptr);}bool Full(){if(_bq.size()==_cap){return true;}return false;}bool Empty(){return _bq.size() == 0;}void Enqueue(const T &in){pthread_mutex_lock(&_mutex);while(Full()){_productor_wait_num++;pthread_cond_wait(&_product_cond,&_mutex);//自动释放锁 ,唤醒自动竞争锁_productor_wait_num--;}_bq.push(in);if(_consumer_wait_num>0){pthread_cond_signal(&_consum_cond);}pthread_mutex_unlock(&_mutex);}void Pop(T* out){pthread_mutex_lock(&_mutex);while(Empty()){_consumer_wait_num++;pthread_cond_wait(&_consum_cond,&_mutex);//自动释放锁 ,唤醒自动竞争锁_consumer_wait_num--;}*out = _bq.front(); _bq.pop();if(_productor_wait_num>0){pthread_cond_signal(&_product_cond);}pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_product_cond);pthread_cond_destroy(&_consum_cond);}
private:std::queue<T> _bq;uint32_t _cap;pthread_mutex_t _mutex;pthread_cond_t _product_cond; // 专⻔给⽣产者提供的条件变量pthread_cond_t _consum_cond;//专门给消费者提供的int _productor_wait_num;int _consumer_wait_num;
};
#endif
Main.cc
#include "BlockQueue.hpp"
struct ThreadDate
{std::string name;BlockQueue<int>* _bq;
};
void *consumer(void *args)
{ThreadDate *dp = static_cast<ThreadDate *>(args);while(true){ sleep(1);int data = 0;dp->_bq->Pop(&data);std::cout<<"删除一个数据"<< data-- <<std::endl;}return nullptr;
}
void *productor(void *args)
{ThreadDate *dp = static_cast<ThreadDate *>(args);int data = 1;while(true){dp->_bq->Enqueue(data);std::cout<<"生产了一个数据"<< data++ <<std::endl;}return nullptr;
}
int main()
{BlockQueue<int> *bq = new BlockQueue<int>();pthread_t d,p;ThreadDate ctd = {"生产者",bq};ThreadDate ptd = {"消费者",bq};// 消费者线程绑定消费者数据,生产者线程绑定生产者数据pthread_create(&d, nullptr, consumer, (void *)&ptd); // 消费者线程用 ptd("消费者")pthread_create(&p, nullptr, productor, (void *)&ctd); // 生产者线程用 ctd("生产者")pthread_join(d,nullptr);pthread_join(p,nullptr);
}
Makefile
blockqueue:Main.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -rf blockqueue
为什么生产和消费者模型高效
- 支持忙闲不均的情况
- 并发指得是生产之前和获取数据之后处理数据两者可以并发处理
4、条件变量的封装
#pragma once
#include <string>
#include <pthread.h>
#include "mutex.hpp"
class Cond
{
public:Cond(){pthread_cond_init(&_cond,nullptr);}void Wait(Mutex &lock){pthread_cond_wait(&_cond,lock.GetMutexOriginal());}void Notify(){pthread_cond_signal(&_cond);}void AllNotify(){pthread_cond_broadcast(&_cond);}~Cond(){pthread_cond_destroy(&_cond);}
private:pthread_cond_t _cond;
};
四、信号量
1、信号量的引入
当我们将资源不是整体使用的时候我们一般会发生两大问题
- 1、放过多的线程进入(使用信号量)
- 2、让不同的线程访问了同一位置(程序员做的)
信号量本质就是对资源的预订机制
看到同一个信号量——》信号量本身就是共享资源——》PV原子性
2、信号量接口
初始化
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表⽰线程间共享,⾮零表⽰进程间共享
value:信号量初始值
如果多个信号量 sem_t sem[n]
销毁
int sem_destroy(sem_t *sem);
等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
发布信号量
功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
3、生产消费模型场景(基于环形队列)

- 为空的时候,必须要让生产者先运行 (因为生产和消费会访问同一个位置,所有必须互斥放入数据,所以生产和消费者的互斥与同步关系)
- 为满的时候,生产和消费又指向同一位置,必须让消费者运行(必须互斥的获取数据)
- 不为空且不为满,生产和消费访问的不是同一位置,此时可以并发执行

代码实现
main.cc
#include "ring_queue.hpp"
#include <iostream>
#include <unistd.h>
void *consumer(void*args)
{RingQueue<int> *ring = static_cast<RingQueue<int> *> (args);int data = 0;while(true){ring->Pop(&data);std::cout<<"删除了一个数据"<< data <<std::endl;}return nullptr;
}
void *productor(void*args)
{sleep(1);RingQueue<int> *ring = static_cast<RingQueue<int> *> (args);int data = 1;while(true){ring->Enqueue(data);std::cout <<"生成了一个数据"<< data++ <<std::endl;}return nullptr;
}
int main()
{pthread_t p,d;RingQueue<int> *ring = new RingQueue<int>();pthread_create(&p,nullptr,consumer,(void*)ring);pthread_create(&d,nullptr,productor,(void*)ring);pthread_join(p,nullptr);pthread_join(d,nullptr);delete ring;
}
Sem.hpp
#pragma once
#include <semaphore.h>
class Sem
{
public:Sem(int num):_num(num){int n = sem_init(&_sem, 0, _num);(void)n;}void P(){int n = sem_wait(&_sem);(void)n;}void V(){int n = sem_post(&_sem);(void)n;}~Sem(){int n = sem_destroy(&_sem);(void)n;}private:sem_t _sem;int _num;
};
ring_queue.hpp
#pragma once
#include<iostream>
#include<pthread.h>
#include <vector>
#include "Sem.hpp"
const int grep = 5;
template<typename T>
class RingQueue
{
public:RingQueue(int cap = grep):_cap(cap),_ring_queue(_cap),_space_sem(_cap),_data_sem(0),_p_step(0),_c_step(0){}void Enqueue(T& in){_space_sem.P();_ring_queue[_p_step++] = in;_p_step %= _cap;_data_sem.V();}void Pop(T* out){_data_sem.P();*out = _ring_queue[_c_step++];_c_step %= _cap;_space_sem.V();}~RingQueue(){}
private:int _cap;std::vector<T> _ring_queue;Sem _space_sem;Sem _data_sem;int _p_step;int _c_step;
};
上述代码,没有加锁,能保证数据的安全吗
使用信号量,变相的完成了为空和为满时的同步和互斥
为什么没有判断资源是否就绪
申请信号量:对资源的预定机制
整体访问,局部访问
如果将信号量,初始值设置为1,本质就是锁
五、日志书写
日志格式

main.cc
#include "Logger.hpp"
int main()
{FileLogStrategy();LOG(Loglevel::DEBUG) << "hello world";LOG(Loglevel::DEBUG) << "hello world";LOG(Loglevel::DEBUG) << "hello world";// ENABLE_FILE_LOG_STRATEGY();LOG(Loglevel::DEBUG) << "hello world";LOG(Loglevel::DEBUG) << "hello world";LOG(Loglevel::WARNING) << "hello world";return 0;
}
Logger.hpp
#pragma once
#include <iostream>
#include <string>
#include "mutex.hpp"
#include <filesystem>
#include <fstream>
#include <ctime>
#include <sstream>
#include <memory>
#include <unistd.h>
enum class Loglevel
{DEBUG,INFO,WARNING,ERROR,FATAL
};
std::string LogLevelToString(Loglevel level)
{switch (level){case Loglevel::DEBUG:return "DEBUG";case Loglevel::INFO:return "INFO";case Loglevel::WARNING:return "WARNING";case Loglevel::ERROR:return "ERROR";case Loglevel::FATAL:return "FATAL";default:return "UNKNOWN";}}
std::string GetCurrTime()
{time_t tm = time(nullptr);struct tm curr;localtime_r(&tm,&curr);char timebuffer[64];snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return timebuffer;
}
class LogStrategy
{
public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string messgase) = 0;
};
class ConsoleLogStrategy : public LogStrategy
{
public:~ConsoleLogStrategy(){}void SyncLog(const std::string messgase) override{{Lockguard lockguard(&_lock);std::cout << messgase << std::endl;}}
private:Mutex _lock;
};
const std::string path ="log";
const std::string filename = "test.log";
class FileLogStrategy: public LogStrategy
{
public:FileLogStrategy(const std::string logpath = path, const std::string logfilename = filename):_logpath(logpath),_logfilename(logfilename){{ Lockguard lockguard(&_lock);if (std::filesystem::exists(_logpath)){return;}try{std::filesystem::create_directories(_logpath);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << '\n';}}}~FileLogStrategy(){}void SyncLog(const std::string messgase) override{{Lockguard lockguard(&_lock);std::string target = _logpath;target += '/';target += _logfilename;std::ofstream out(target.c_str(), std::ios::app);if (!out.is_open()){return;}out << messgase << std::endl;out.close();}}
private:std::string _logpath;std::string _logfilename;Mutex _lock;
};
class Logger
{
public:Logger(){}void EnableFileLogStrategy(){_strategy = std::make_unique<FileLogStrategy>();}void EnableConsoleLogStrategy(){_strategy = std::make_unique<ConsoleLogStrategy>();}class LogMessage{public:LogMessage(Loglevel level, std::string &filename, int line, Logger &logger):_time(GetCurrTime()),_level(level),_pid(getpid()),_filename(filename),_line(line),_logger(logger){std::stringstream ss;ss << "[" << _time << "]"<< "[" << LogLevelToString(_level) << "]"<< "[" << _pid << "]"<< "[" << _filename << "]"<< "[" << _line << "]"<< "-";_loginfo = ss.str();}template<typename T>LogMessage & operator<<(const T& info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if(_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _time;Loglevel _level;pid_t _pid;std::string _filename;int _line;std::string _loginfo;Logger &_logger;};LogMessage operator()(Loglevel type, std::string filename, int line){return LogMessage(type, filename, line,*this);}~Logger(){}
private:std::unique_ptr<LogStrategy> _strategy;
};
Logger logger;#define LOG(type) logger(type, __FILE__, __LINE__)#define FileLogStrategy() logger.EnableFileLogStrategy()#define ConsoleLogStrategy() logger.EnableConsoleLogStrategy()
mutex.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
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);}pthread_mutex_t *GetMutexOriginal(){return &lock;}
private:pthread_mutex_t lock;
};
class Lockguard
{
public:Lockguard(Mutex* mutexgurd):_mutexgurd(mutexgurd){_mutexgurd->Lock();}~Lockguard(){_mutexgurd->Unlock();}
private:Mutex *_mutexgurd;
};
Makefile
logger:main.ccg++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:rm -rf logger
六、线程池
Cond.hpp
#pragma once
#include <string>
#include <pthread.h>
#include "mutex.hpp"
class Cond
{
public:Cond(){pthread_cond_init(&_cond,nullptr);}void Wait(Mutex &lock){pthread_cond_wait(&_cond,lock.GetMutexOriginal());}void Notify(){pthread_cond_signal(&_cond);}void AllNotify(){pthread_cond_broadcast(&_cond);}~Cond(){pthread_cond_destroy(&_cond);}
private:pthread_cond_t _cond;
};
Logger.hpp
#pragma once
#include <iostream>
#include <string>
#include "mutex.hpp"
#include <filesystem>
#include <fstream>
#include <ctime>
#include <sstream>
#include <memory>
#include <unistd.h>
enum class Loglevel
{DEBUG,INFO,WARNING,ERROR,FATAL
};
std::string LogLevelToString(Loglevel level)
{switch (level){case Loglevel::DEBUG:return "DEBUG";case Loglevel::INFO:return "INFO";case Loglevel::WARNING:return "WARNING";case Loglevel::ERROR:return "ERROR";case Loglevel::FATAL:return "FATAL";default:return "UNKNOWN";}}
std::string GetCurrTime()
{time_t tm = time(nullptr);struct tm curr;localtime_r(&tm,&curr);char timebuffer[64];snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return timebuffer;
}
class LogStrategy
{
public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string messgase) = 0;
};
class ConsoleLogStrategy : public LogStrategy
{
public:~ConsoleLogStrategy(){}void SyncLog(const std::string messgase) override{{Lockguard lockguard(&_lock);std::cout << messgase << std::endl;}}
private:Mutex _lock;
};
const std::string path ="log";
const std::string filename = "test.log";
class FileLogStrategy: public LogStrategy
{
public:FileLogStrategy(const std::string logpath = path, const std::string logfilename = filename):_logpath(logpath),_logfilename(logfilename){{ Lockguard lockguard(&_lock);if (std::filesystem::exists(_logpath)){return;}try{std::filesystem::create_directories(_logpath);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << '\n';}}}~FileLogStrategy(){}void SyncLog(const std::string messgase) override{{Lockguard lockguard(&_lock);std::string target = _logpath;target += '/';target += _logfilename;std::ofstream out(target.c_str(), std::ios::app);if (!out.is_open()){return;}out << messgase << std::endl;out.close();}}
private:std::string _logpath;std::string _logfilename;Mutex _lock;
};
class Logger
{
public:Logger(){}void EnableFileLogStrategy(){_strategy = std::make_unique<FileLogStrategy>();}void EnableConsoleLogStrategy(){_strategy = std::make_unique<ConsoleLogStrategy>();}class LogMessage{public:LogMessage(Loglevel level, std::string &filename, int line, Logger &logger):_time(GetCurrTime()),_level(level),_pid(getpid()),_filename(filename),_line(line),_logger(logger){std::stringstream ss;ss << "[" << _time << "]"<< "[" << LogLevelToString(_level) << "]"<< "[" << _pid << "]"<< "[" << _filename << "]"<< "[" << _line << "]"<< "-";_loginfo = ss.str();}template<typename T>LogMessage & operator<<(const T& info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if(_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _time;Loglevel _level;pid_t _pid;std::string _filename;int _line;std::string _loginfo;Logger &_logger;};LogMessage operator()(Loglevel type, std::string filename, int line){return LogMessage(type, filename, line,*this);}~Logger(){}
private:std::unique_ptr<LogStrategy> _strategy;
};
Logger logger;#define LOG(type) logger(type, __FILE__, __LINE__)#define FileLogStrategy() logger.EnableFileLogStrategy()#define ConsoleLogStrategy() logger.EnableConsoleLogStrategy()
Mutex.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
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);}pthread_mutex_t *GetMutexOriginal(){return &lock;}
private:pthread_mutex_t lock;
};
class Lockguard
{
public:Lockguard(Mutex* mutexgurd):_mutexgurd(mutexgurd){_mutexgurd->Lock();}~Lockguard(){_mutexgurd->Unlock();}
private:Mutex *_mutexgurd;
};
Thread.hpp
#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <pthread.h>
#include <string>
#include <sys/syscall.h>
#include <functional>
#include <unistd.h>
#include "Logger.hpp"
#define get_lwp_id() syscall(SYS_gettid)
using fun_t = std::function<void(const std::string& name)>;
std::string defaultname = "none";
class Thread
{
public:Thread(fun_t fun,std::string name = defaultname):_name(name),_fun(fun),_runing(false){}static void* start_routine(void * args){Thread *self = static_cast<Thread *>(args);self->_runing = true;self->_lwpid = get_lwp_id();self->_fun(self->_name);pthread_exit((void*)0);}void Start(){int n = pthread_create(&_tid,nullptr,start_routine,this);//因为类中参数自带有一个变量this指针,所以这个参数必须填这个if(n==0){LOG(Loglevel::INFO) << "thread start success";}}void Stop(){int n = pthread_cancel(_tid);LOG(Loglevel::INFO) << "thread stop success";(void) n;}void join(){if(!_runing){return;}int n = pthread_join(_tid,nullptr);if(n==0){LOG(Loglevel::INFO) << "thread join success";}}~Thread(){}
private:pthread_t _tid;pid_t _lwpid;std::string _name;fun_t _fun;bool _runing;
};#endif
ThreadPool.hpp
#pragma once
#include <queue>
#include <vector>
#include "mutex.hpp"
#include "Cond.hpp"
#include "Thread.hpp"
#include "Logger.hpp"
#include <iostream>
const int threadnum = 3;
template<class T>
class ThreadPool
{
public:bool QueueIsEmpty(){return _q.empty();}void Ruine(const std::string& name){while(true){T t;{Lockguard lockguard(&_lock);while(QueueIsEmpty()&&_runing){_wait_num++;_cond.Wait(_lock);_wait_num--;}if(!_runing && QueueIsEmpty()){LOG(Loglevel::INFO) << "线程退出 && 线程任务队列为空" << name << ' ' << "退出";break;}t = _q.front();_q.pop();}t();LOG(Loglevel::INFO) << name << ' ' << t.ResultToString(); }}ThreadPool(int num = threadnum):_num(num),_runing(false),_wait_num(0){for(int i = 0; i<_num; i++){std::string name = "Thread" + std::to_string(i);_threads.emplace_back([this](const std::string& name){this->Ruine(name);},name);}LOG(Loglevel::INFO) << "threadpool creat success";}void Start(){if(_runing){return;}_runing = true;for(auto &e : _threads){e.Start();}LOG(Loglevel::INFO) << "threadpool start success";}void Stop(){if(!_runing){return;}_runing = false;_cond.AllNotify();}void Wait(){for(auto &e : _threads){e.join();}LOG(Loglevel::INFO) << "threadpool wait success";}void Enqueue(T& t){{Lockguard lockguard(&_lock);_q.push(t);if(_wait_num>0){_cond.Notify();}}}~ThreadPool(){}
private:std::queue<T> _q;std::vector<Thread> _threads;Mutex _lock;Cond _cond;int _num;bool _runing;int _wait_num;
};
Task.hpp
#include <functional>
#include <unistd.h>
#include <iostream>
class Task
{
public:Task(){}Task(int x, int y):_x(x),_y(y){}void Execute(){_result = _x + _y;}void operator()(){sleep(1);Execute();}void Print(){std::cout << _x << '+' << _y << '=' << _result << std::endl;}std::string ResultToString(){std::stringstream ss;ss << _x << '+' << _y << '=' << _result;return ss.str();}~Task(){}
private:int _x;int _y;int _result;
};
main.cc
#include "ThreadPool.hpp"
#include "memory"
#include "Task.hpp"
int main()
{ConsoleLogStrategy();std::unique_ptr<ThreadPool<Task>> tp = std::make_unique<ThreadPool<Task>>(5);tp ->Start();int s = 5;while(s--){Task t(s,s);tp->Enqueue(t);}sleep(5);tp->Stop();tp->Wait();return 0;
}
Makefile
ThreadPool:main.ccg++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:rm -rf ThreadPool
七、线程安全的单例模式
1、单例模式的概念
某写类,只应该具有一个对象,就称之为单例
2、实现方式
(1)饿汉实现方式
吃完饭,立刻洗碗,就是饿汉方式,因为下⼀顿吃的时候可以立刻拿着碗就能吃饭
data:在还未使用它的时候,就存在了(进程加载时类对象被创建)
template <typename T>
class Singleton
{static T data;
public:static T* GetInstance() {return &data;}
};
(2)懒汉实现方式
吃完饭, 先把碗放下, 然后下⼀顿饭⽤到这个碗了再洗碗, 就是懒汉⽅式
template <typename T>
class Singleton
{static T* inst;
public:static T* GetInstance() {if (inst == NULL) {inst = new T();} return inst;}
};
八、线程安全和重入问题
1、概念
线程安全:就是多个线程在访问共享资源时,能够正确地执行,不会相互干扰或破坏彼此的执行结果。⼀般而言,多个线程并发同⼀段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或者静态变量进行操作,并且没有锁保护的情况下,容易出现该问题。
重⼊:同⼀个函数被不同的执行流调用,当前⼀个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。⼀个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重⼊函数,否则,是不可重⼊函数

2、结论

九、死锁问题
1、概念
- 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用且不会释放的资源,而处于的一种永久等待状态。
- 为了方便表述,假设现在线程 A、线程 B 必须同时持有锁 1 和锁 2,才能进行后续资源的访问。
2、死锁产生的必须条件
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源不放
- 不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
STL中的容器是线程安全的?
不是.
原因是, STL 的设计初衷是将性能挖掘到极致, 而⼀旦涉及到加锁保证线程安全, 会对性能造成巨⼤的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全

