线程,信号量

您所在的位置:网站首页 互斥信号量的初始值是多少 线程,信号量

线程,信号量

2023-08-29 10:14| 来源: 网络整理| 查看: 265

多线程 信号量

信号量:可以实现进程/线程间同步与互斥。 信号本质就是一个计数器 + pcb等待队列

信号量同步的实现:

 通过自身的计数器对资源进行计数,并且通过计数器的资源计数,判断进程/线程是否能够符合访问资源的条件,若符合就可以访问,若不符合则提供的接口使进程/线程阻塞;其他进程/线程促使条件满足之后,可以唤醒pcb等待队列上的pcb

信号量互斥的实现:

 保证计数器的计数不大于1,保证了资源只有一个,同一时间只有一个进程/线程能够访问资源,实现互斥。

代码操作 1.定义信号量:```sem_t```

2.初始化信号量 int set_t init(sem_t *sem, int pshared, unsigned int value) sem: 定义的信号量变量; pshare: 0-用于线程间/ 非0- 用于进程间 value: 初始化信号量初值---初始资源数量有多少就是多少;成功返回0,失败返回-1 3.在访问临界资源之前,先访问信号量,判断是否能够访问: 计数-1 int sem_wait(sem_t* sem);---通过自身计数,判断是否满足访问条件,不满足则一直阻塞线程/进程 int sem_trywait(sem_t* sem);--- 不满足 则立即报错返回 int sem_timewait(sem_t* sem, const struct timespec* abs_timeout)---不满足则等待指定时间,超时后报错返回 4.促使条件满足,唤醒阻塞线程/进程: 计数+1 int sem_post(sem_t* sem);---通过信号量唤醒自己阻塞队列上的pcb 5.销毁信号量 int sem_destroy(sem_t* sem); 通过信号量实现一个生产者消费者模型—线程安全的阻塞队列

#define QUEUE_MAX 5 class RingQueue{ public: RingQueue(int maxq = QUEUE_MAX):_queue(maxq) ,_capacity(maxq) ,_step_read(0) ,_step_write(0){ sem_init(&_lock, 0, 1); // 用于实现互斥锁 sem_init(&_sem_data, 0, 0); //数据空间计数初始为0 sem_init(&_sem_idle, 0, maxq); //空闲空间计数器初始为节点个数 } ~RingQueue(){ sem_destroy(&_lock); sem_destroy(&_sem_data); sem_destroy(&_sem_idle); } bool push(int data){ //1.判断是否能够访问资源----空闲空间的判断,空闲空间计数-1 sem_wait(&sem_idle); //2.能访问,则加锁,保护访问过程 sem_wait(&lock);//lock计数只有不大于1,当前访问则-1,别人就不能访问了 //3.资源的访问 _queue[_step_write] = data; _step_write = (_step_write + 1)%_capacity; //走到最后,从头开始 //4.解锁 sem_post(&lock); //lock计数+1;唤醒其他加锁阻塞的线程; //5.入队数据之后,数据空间计数+1, 唤醒消费者 sem_post(&_sem_data); return true; } bool pop(int* data){ sem_wait(&sem_data); sem_wait(&lock); *data = _queue[step_read]; _step_read = (_step_read + 1)%_capacity; sem_post(&lock); sem_post(&_sem_data); return true; } private: vector _queue; //数组,vector需要初始化容量 int _capacity; //队列容量 int _step_read; //获取数据的位置下标 int _step_write; //写入数据的位置下标 sem_t _lock; sem_t _sem_data; //这个信号量用于对具有数据的空间进行计数---对于消费者来说有数据的空间计数 >0 的时候才能取出数据---初始为0 sem_t _sem_idle; //这个信号量用于对空闲空间进行计数---对于生产者来说空闲空间计数 >0 的时候才能写入数据---初始为节点个数 }; 线程池

线程的池子,有很多线程,但是数量不会超过池子的限制。----需要用到多执行流进行任务处理的时候,就从池子中取出一个线程去处理  应用场景:有大量数据请求处理,需要多执行流并发/并行处理  若是一个请求的到来伴随着一个线程的创建去处理,则会产生一些风险以及一些不必要的消耗 1.线程若不限制数量的创建,在峰值压力下,线程创建过多,资源耗尽,有程序崩溃的风险 2.处理一个任务的时间: 创建线程的时间t1+任务处理时间t2+线程销毁的时间t3 = T,若t2/T比例占据不够高,表示大量的资源用于线程的创建和销毁成本上,因此线程池使用已经创建好的线程进行循环任务处理,就避免了大量的线程的频繁创建与销毁的时间成本。

自主编写一个线程池

 大量线程(每个线程中都是进行循环的任务处理) + 任务缓冲队列 线程的入口函数,都是在创建线程的时候,就固定传入的,导致线程池中的线程进行任务处理方式过于单一----灵活性太差。  若任务队列中的任务,不仅仅是单纯的数据,而是包含任务处理方法在内的数据,这时候,线程池中的线程只需要使用传入的方法,处理传入的数据即可,不需要关心什么是数据,如何处理----提高灵活性。

//threadPool.hpp #include #include #include typedef void(*handler_t)(int data); class Mytask{ private: int _data;//要处理的数据 handler_t _handler(handler_t handler); //处理数据的方法 public: Mytask(){ } void setTask(int data, handler_t handler){ _data = data; _handler = handler; } void Run(){ //外部只需要调用Run不需要关心任务如何处理 return _handler(_data); } }; class ThreadPool{ private: int thr_max; //定义线程池中线程的最大数量--初始化时创建相应数量的线程即可 queue _queue; pthread_mutex_t _mutex; //实现_queue操作的安全性 pthread_cond_t _cond; //实现线程池中消费者线程的同步 public: ThreadPool(int max_thr = MAX_THREAD):thr_max(max_thr){ pthread_mutex_init(&_mutex, NULL); pthread_con_init(&_cond, NULL); for(int i = 0; i printf("thread create error!"); exit(-1); } } } ~ThreadPool(){ pthread_mutex_destroy(&_mutex); pthread_con_destroy(&_cond); } bool TaskPush(Mytask &task){ pthread_mutex_lock(&_mutex); _queue.push(task); pthread_mutex_unlock(&_mutex); pthread_cond_broadcast(&_cond);//入队后唤醒所有线程,谁抢到谁处理 return true; } void* thr_start(void*){ //不断地从任务队列中取出任务,执行Run接口就行 while(1){ pthread_mutex_lock(&_mutex); while(_queue.empty()){ pthread_cond_wait(&_mutex, &_cond); } Mytask task; task = _queue.front(); _queue.pop(); pthread_mutex_unlock(&_mutex); task.Run(); //任务的处理要放在解锁之外 } return NULL; } }; //threadPool.cpp #include"threadPool.hpp" void test_func(int data){ int sec = (data % 3) + 1; printf("tid: %d -- get data: %d -- sleep: %d\n", pthread_self(), i ,sec); sleep(sec); } void test0_func(int data){ printf("tid: %d --- test0_func", pthread_self()); sleep(1); } int main(){ ThreadPool pool; for(int i = 0; i task.setTask(i, test_func); } else{ task.setTask(i, test0_func); } } } 线程安全

 STL中的容器是线程安全的吗? ----不是  智能指针是线程安全的吗 ---- unique_ptr 因为局部操作/shared_ptr原子操作,不涉及线程安全问题

线程安全的单例模式:

  单例模式:是非常典型常用的一种设计模式(大佬们针对典型场景设计的解决方案)   一份资源只能被申请加载一次/一个实例化的对象共用同一份资源 如何实现: 饿汉方式 / 懒汉方式  饿汉方式:资源在程序初始化的时候就去加载 ,后面使用的时候就能直接使用----对于饿汉方式来说,想要吃饭的时候就能立即吃饭   优势:使用的时候比较流畅  劣势:有时候加载上用不上的资源,会导致程序初始化的时间比较慢  实现方式:使用static----将一个成员变量设置为静态变量,则所有对象使用同一分资源,并且在程序初始化时就会申请资源----不涉及线程安全

template class Singleton { static T data; public: static T* GetInstance() { return &data; } };

 懒汉方式:资源在使用的时候发现还没有加载,则申请加载   优势:程序初始化比较快  劣势:第一次运行某个模块就比较慢,因为这时候在加载某个资源  实现方式: 1.使用static保证所有对象使用同一份资源 2.使用volatile, 防止编译器过度优化 3.实现线程安全,保证资源判断以及申请过程是安全的 4.外部二次判断,避免锁冲突,以及避免资源已经加载成功每次获取都要加载,以及带来的锁冲突

template 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; } };


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3