美工网站设计是什么,快速建设网站免费视频教程,wordpress 邀请,asp.net开发微网站开发文章目录 ☀️一、POSIX信号量#x1f33b;1.引入#x1f33b;2.信号量的概念#x1f33b;3.信号量函数 ☀️二、基于环形队列的生产消费模型#x1f33b;1.理解环形队列#x1f33b;2.代码案例 ☀️三、线程池☀️四、线程安全的单例模式#x1f33b;1.单例模式与设计模… 文章目录 ☀️一、POSIX信号量1.引入2.信号量的概念3.信号量函数 ☀️二、基于环形队列的生产消费模型1.理解环形队列2.代码案例 ☀️三、线程池☀️四、线程安全的单例模式1.单例模式与设计模式2.饿汉实现方式和懒汉实现方式 ☀️五、STL,智能指针和线程安全☀️六、其他常见的各种锁了解 ☀️一、POSIX信号量
1.引入
回顾我们之前学习的线程知识我们知道一个线程访问临界资源时是需要满足生产消费条件的。因此我们对公共资源的访问逻辑一般为加锁、检测、操作等待或执行、解锁。那么我们有没有一种办法可以让我们提前得知检测条件是否满足呢这就要涉及到我们今天要学习的知识 - 信号量了。
2.信号量的概念 什么是信号量 信号量本质上就是一把计数器。 是一把衡量衡量临界资源中资源多少的计数器。 我们对临界资源进行整体加锁就默认了我们对这个资源是整体使用的。但是实际情况可能是只有一份公共资源但是允许访问资源不同区域的情况。 申请信号量的本质对临界资源中特定小块资源进行 预定 的机制类似于网上预定电影票。 通过先申请信号量计数器再执行的方式就可以实现在未来某个线程占有一部分临界资源与其他线程占用同一临界资源的其他部分不冲突。 信号量底层原理 设 sem_t sem 10sem_t是计数器的类型sem 10表示这块临界资源可以分为10块小资源。 sem–申请资源必须保证操作的原子性我们用 P代表这个操作。 sem归还资源必须保证操作的原子性我们用 V代表这个操作。 信号量核心操作PV原语。
3.信号量函数
POSIX信号量和SystemV信号量作用相同都是用于同步操作达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。 初始化信号量
#include semaphore.h
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数pshared:0表示线程间共享非零表示进程间共享value信号量初始值销毁信号量
int sem_destroy(sem_t *sem);等待信号量
功能等待信号量会将信号量的值减1
int sem_wait(sem_t *sem); //P()发布信号量
功能发布信号量表示资源使用完毕可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()☀️二、基于环形队列的生产消费模型
1.理解环形队列 环形队列采用数组模拟用模运算来模拟环状特性 数组有num格空间当 i指向 num格的位置的时候%模上 num就可以回到起始位置。 生产和消费什么时候能访问同一个位置 队列空的时候。队列满的时候。其他情况生产者和消费者根本上访问的区域是不同的。 环形队列的生产消费问题需要 遵守的规则/完成的核心工作 生产者不能超过消费者。消费者不能将生产者套一个圈及以上。生产者消费者指向统一资源的情况a.队列全空生产者需要先执行b.队列全满消费者需要先执行。 在环形队列当中大部分情况下即满足上述规则单生产和单消费是可以并发执行的。 信号量是用来衡量临界资源中的资源数量的 对于生产者而言看重什么队列中的剩余空间 – 空间资源定义一个信号量。对于消费者而言看重什么放入队列中的数据 – 数据资源定义一个信号量。 我们给生产者和消费者各设定一个计数器就可以很简单的进行多线程间的同步过程。 双信号量运行原理伪代码 对生产者而言prodocter_sem 10 //申请信号量成功则向下运行失败则阻塞comsumer_sem 0;P(prodocter_sem); //prodocter_sem--//从事生产活动 -- 把数据放到队列中V(comsumer_sem) //comsumer_sem对消费者而言P(comsumer_sem); //comsumer_sem--//从事消费活动 -- 把数据从队列中拿出V(prodocter_sem) //prodocter_sem 当我们的生产信号量减到0时再申请空间资源就申请不到了因此就可以满足生产消费的三个条件。 2.代码案例
我们现在有信号量这个计数器就可以很简单的进行多线程间的同步过程。下面的代码例子是两个线程分别同时进行生产消费。
#include iostream
#include vector
#include stdlib.h
#include semaphore.h
#include pthread.h#define NUM 16class RingQueue{
private:std::vectorint q;int cap;sem_t data_sem;sem_t space_sem;int consume_step;int product_step;public:RingQueue(int _cap NUM) :q(_cap), cap(_cap){sem_init(data_sem, 0, 0);sem_init(space_sem, 0, cap);consume_step 0;product_step 0;}void PutData(const int data){sem_wait(space_sem); // Pq[consume_step] data;consume_step;consume_step % cap;sem_post(data_sem); //V}void GetData(int data){sem_wait(data_sem);data q[product_step];product_step;product_step % cap;sem_post(space_sem);}~RingQueue(){sem_destroy(data_sem);sem_destroy(space_sem);}
};void* consumer(void* arg)
{RingQueue* rqp (RingQueue*)arg;int data;for (; ; ) {rqp-GetData(data);std::cout Consume data done : data std::endl;sleep(1);}
}//more faster
void* producter(void* arg)
{RingQueue* rqp (RingQueue*)arg;srand((unsigned long)time(NULL));for (; ; ) {int data rand() % 1024;rqp-PutData(data);std::cout Prodoct data done: data std::endl;// sleep(1);}
}int main()
{RingQueue rq;pthread_t c, p;pthread_create(c, NULL, consumer, (void*)rq);pthread_create(p, NULL, producter, (void*)rq);pthread_join(c, NULL);pthread_join(p, NULL);
}☀️三、线程池
/*threadpool.h*/
/* 线程池:
*一种线程使用模式。线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着
监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利
用还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。* 线程池的应用场景
* 1. 需要大量的线程来完成任务且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务使用线程池技
术是非常合适的。因为单个任务小而任务数量巨大你可以想象一个热门网站的点击次数。 但对于长时间的任务比如一个
Telnet连接请求线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
* 2. 对性能要求苛刻的应用比如要求服务器迅速响应客户请求。
* 3. 接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池情
况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题短时间内产生大量线程可能使内存到达极限
出现错误.* 线程池的种类
* 线程池示例
* 1. 创建固定数量线程池循环从任务队列中获取任务对象
* 2. 获取到任务对象后执行任务对象中的任务接口
*//*threadpool.hpp*/
#ifndef __M_TP_H__
#define __M_TP_H__
#include iostream
#include queue
#include pthread.h#define MAX_THREAD 5
typedef bool (*handler_t)(int);class ThreadTask
{
private:int _data;handler_t _handler;
public:ThreadTask() :_data(-1), _handler(NULL) {}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;std::queueThreadTask* _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(NULL);}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 (ThreadPool*)arg;while (1) {tp-LockQueue();while (tp-IsEmpty()) {tp-ThreadWait();}ThreadTask* tt;tp-PopTask(tt);tp-UnLockQueue();tt-Run();delete tt;}return NULL;}public:ThreadPool(int max MAX_THREAD) :_thread_max(max), _thread_cur(max),_tp_quit(false) {pthread_mutex_init(_lock, NULL);pthread_cond_init(_cond, NULL);}~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, NULL, thr_start, this);if (ret ! 0) {std::cout create pool thread error\n;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();usleep(1000);}return true;}
};
#endif/*main.cpp*/
bool handler(int data)
{srand(time(NULL));int n rand() % 5;printf(Thread: %p Run Tast: %d--sleep %d sec\n, pthread_self(), data, n);sleep(n);return true;
}
int main()
{int i;ThreadPool pool;pool.PoolInit();for (i 0; i 10; i) {ThreadTask* tt new ThreadTask(i, handler);pool.PushTask(tt);}pool.PoolQuit();return 0;
}g -stdc0x test.cpp -o test -pthread -lrt☀️四、线程安全的单例模式
1.单例模式与设计模式
什么是单例模式
单例模式是一种 “经典的, 常用的, 常考的” 设计模式。
什么是设计模式
IT行业这么火涌入的人很多俗话说林子大了啥鸟都有。大佬和菜鸡们两极分化的越来越严重为了让菜鸡们不太拖大佬的后腿于是大佬们针对一些经典的常见的场景给定了一些对应的解决方案这个就是 设计模式。
单例模式的特点
某些类, 只应该具有一个对象(实例), 就称之为单例.
例如一个男人只能有一个媳妇.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.
2.饿汉实现方式和懒汉实现方式
[洗碗的例子]
吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度. 饿汉方式实现单例模式
template typename T
class Singleton {static T data;
public:static T* GetInstance() {return data;}
};只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例. 懒汉方式实现单例模式
template typename T
class Singleton {static T* inst;
public:static T* GetInstance() {if (inst NULL) {inst new T();}return inst;}
};上述代码存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.
懒汉方式实现单例模式(线程安全版本)
// 懒汉模式, 线程安全
template typename 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;}
};注意事项:
加锁解锁的位置双重 if 判定, 避免不必要的锁竞争volatile关键字防止过度优化 ☀️五、STL,智能指针和线程安全
STL中的容器是否是线程安全的?
不是.
原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.
智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数. ☀️六、其他常见的各种锁了解
悲观锁在每次取数据时总是担心数据会被其他线程修改所以会在取数据前先加锁读锁写锁行锁等当其他线程想要访问数据时被阻塞挂起。乐观锁每次取数据时候总是乐观的认为数据不会被其他线程修改因此不上锁。但是在更新数据前会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式版本号机制和CAS操作。CAS操作当需要更新数据时判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败失败则重试一般是一个自旋的过程即不断重试。自旋锁公平锁非公平锁… 多线4 的知识大概就讲到这里啦博主后续会继续更新更多C 和 Linux的相关知识干货满满如果觉得博主写的还不错的话希望各位小伙伴不要吝啬手中的三连哦你们的支持是博主坚持创作的动力