当前位置: 首页 > news >正文

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 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全

http://www.dtcms.com/a/557224.html

相关文章:

  • 基于数据增强与对抗学习的门诊电子病历(EMR)文本分类python编程
  • 企业网站seo推广技巧建设视频网站设计意义
  • VSCode的插件配置同步到gitee
  • 短剧广告联盟 APP 用户画像:基于观看行为的广告精准投放模型
  • 找快照网站查询网站建设博采
  • [论文阅读] AI+ | AI如何重塑审计行业?从“手工筛查”到“智能决策”:AI审计的核心逻辑与未来路径
  • 论文精读:A review on multi-view learning(多视图学习综述)
  • 长宁制作网站网站建设属于会计哪个科目
  • 波动率建模(三)Heston模型及其Python实现
  • 左侧 导航 网站泰安信誉好的网络推广公司
  • python 初学2
  • 51单片机基础-LCD12864液晶显示
  • 在openSUSE-Leap-15.6-DVD-x86_64-Media自制应用软件离线包——备份91个视频解码器的rpm包
  • 《云原生基础设施》
  • dedecms手机网站开发怎么在网站底部添加备案号
  • 2019个人建网站利用关键词进网站后台
  • 物联网技术与应用传感器学习笔记
  • 云架构设计与实践:从基础到未来趋势
  • 【SDIO】【SDMMC】硬件层
  • 算法 - 差分
  • 定制制作网站哪家好北京seo关键词
  • Transformer:Decoder 中,Cross-Attention 所用的 K(Key)和 V(Value)矩阵,是如何从 Encoder 得到的
  • 矩阵系统全面解析:构建智能营销体系的核心引擎
  • 灵山县建设局网站设计论坛最好的网站
  • 整体设计 全面梳理复盘 之14 原型表结构深化与组织表关联重构
  • 使用 GitLab CI/CD 为 Linux 创建 RPM 包(一)
  • Go语言中error的保姆级认知学习教程,由易到难
  • Go语言中通过get请求获取api.open-meteo.com网站的天气数据
  • 哪些网站可以做微课技术支持 英铭网站建设
  • STM32位带操作理论实践