Linux 12:多线程2

1. 生产者消费者模型

        生产者消费者模型有三种关系,两个角色,一个交易场所。

三种关系:

        生产者之间是什么关系?竞争 - 互斥

        消费者和消费者之间?竞争 - 互斥

        消费者和消费者之间?互斥和同步

两个角色:

        生产者和消费者

一个交易场所:

        内存空间

1-1. 为何要使用生产者消费者模型

        生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

1-2. 生产者消费者模型优点

  •  解耦
  • 支持并发
  • 支持忙闲不均

2. 基于BlockingQueue的生产者消费者模型

BlockingQueue

        在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。

2-1. C++ queue模拟阻塞队列的生产消费模型

        为了便于理解,我们以单生产者,单消费者,来进行讲解,代码:

#include <iostream>
#include <queue>
#include <stdlib.h>
#include <pthread.h>
#define NUM 8
class BlockQueue
{
private:std::queue<int> q;int cap;pthread_mutex_t lock;pthread_cond_t full;pthread_cond_t empty;private:void LockQueue(){pthread_mutex_lock(&lock);}void UnLockQueue(){pthread_mutex_unlock(&lock);}void ProductWait(){pthread_cond_wait(&full, &lock);}void ConsumeWait(){pthread_cond_wait(&empty, &lock);}void NotifyProduct(){pthread_cond_signal(&full);}void NotifyConsume(){pthread_cond_signal(&empty);}bool IsEmpty(){return (q.size() == 0 ? true : false);}bool IsFull(){return (q.size() == cap ? true : false);}public:BlockQueue(int _cap = NUM) : cap(_cap){pthread_mutex_init(&lock, NULL);pthread_cond_init(&full, NULL);pthread_cond_init(&empty, NULL);}voidPushData(const int &data){LockQueue();while (IsFull()){NotifyConsume();std::cout << "queue full, notify consume data, product stop." << std::endl;ProductWait();}q.push(data);// NotifyConsume();UnLockQueue();}voidPopData(int &data){LockQueue();while (IsEmpty()){NotifyProduct();std::cout << "queue empty, notify product data, consume stop." << std::endl;ConsumeWait();}data = q.front();q.pop();// NotifyProduct();UnLockQueue();}~BlockQueue(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&full);pthread_cond_destroy(&empty);}
};
void *consumer(void *arg)
{BlockQueue *bqp = (BlockQueue *)arg;int data;for (;;){bqp->PopData(data);std::cout << "Consume data done : " << data << std::endl;}
}
// more faster void *producter(void *arg)
{BlockQueue *bqp = (BlockQueue *)arg;srand((unsigned long)time(NULL));for (;;){int data = rand() % 1024;bqp->PushData(data);std::cout << "Prodoct data done: " << data << std::endl;// sleep(1);}
}
int main()
{BlockQueue bq;pthread_t c, p;pthread_create(&c, NULL, consumer, (void *)&bq);pthread_create(&p, NULL, producter, (void *)&bq);pthread_join(c, NULL);pthread_join(p, NULL);return 0;
}

3. POSIX信号量

        POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

3-1. 初始化信号量

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

        pshared:0表示线程间共享,非零表示进程间共享

        value:信号量初始值

3-2. 销毁信号量

 int sem_destroy(sem_t *sem);

3-3. 等待信号量

功能:等待信号量,会将信号量的值减1

int sem_wait(sem_t *sem); //P()

3-4. 发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。

int sem_post(sem_t *sem);//V()

        上一节生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):
 

4. 基于环形队列的生产消费模型

  •  环形队列采用数组模拟,用模运算来模拟环状特性

        环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。

         但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。

#include <iostream>
#include <vector>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#define NUM 16
class RingQueue
{
private:std::vector<int> 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);
}

5. 线程池

        一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

 线程池示例:

  1. 创建固定数量线程池,循环从任务队列中获取任务对象,
  2. 获取到任务对象后,执行任务对象中的任务接口

threadpool.hpp

#pragma once
#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::queue<ThreadTask *> _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();}T hreadTask *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;}
};

main.cpp

#include "threadpool.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
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;
}

6. 线程安全的单例模式

什么是单例模式
        单例模式是一种 "经典的,常用的,常考的" 设计模式。
什么是设计模式
        IT行业这么火,涌入的人很多。俗话说林子大了啥鸟都有,大佬和菜鸡们两极分化的越来越严重。为了让菜鸡们不太拖大佬的后腿,于是大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,这个就是设计模式。

6-1. 单例模式的特点

        某些类, 只应该具有一个对象(实例),就称之为单例。
        在很多服务器开发场景中,经常需要让服务器加载很多的数据 (上百G) 到内存中。此时往往要用一个单例的类来管理这些数据。

6-2. 饿汉实现方式和懒汉实现方式 

        吃完饭,立刻洗碗,这种就是饿汉方式。
        吃完饭,先把碗放下,然后下一顿饭用到这个碗了再洗碗,就是懒汉方式。

  • 懒汉方式最核心的思想是 "延时加载"。从而能够优化服务器的启动速度。

 6-2-1. 饿汉方式实现单例模式

template <typename T>

class Singleton {

        static T data;

public:

        static T* GetInstance() {

                return &data;

        }

};

        只要通过Singleton这个包装类来使用 T 对象,则一个进程中只有一个T对象的实例。

6-2-2. 懒汉方式实现单例模式

template <typename T>

class Singleton {

        static T* inst;

public:

        static T* GetInstance() {

        if (inst == NULL) {

                inst = new T();

        }

        return inst;

        }

};

  • 存在一个严重的问题,线程不安全。
  • 第一次调用GetInstance的时候,如果两个线程同时调用,可能会创建出两份T对象的实例。
  • 但是后续再次调用,就没有问题了。

 6-2-3. 懒汉方式实现单例模式(线程安全版本)

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;

        }

};

注意事项:

  1. 锁解锁的位置。
  2. 双重if判定,避免不必要的锁竞争。
  3. volatile关键字防止过度优化。

7. STL,智能指针和线程安全

7-1.STL中的容器是否是线程安全的?

        不是。
        原因是,STL 的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。
        而且对于不同的容器,加锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)。
        因此STL默认不是线程安全。如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。

7-2. 智能指针是否是线程安全的?

        对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。
        对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效,原子的操作引用计数。

 8. 其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/48529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【07】LLaMA-Factory微调大模型——微调模型导出与微调参数分析

上文介绍了如何对微调后的模型进行使用与简单评估。本文将介绍对微调后的模型进行导出的过程。 一、llama-3微调后的模型导出 首先进入虚拟环境&#xff0c;打开LLaMA-Factory的webui页面 conda activate GLM cd LLaMA-Factory llamafactory-cli webui 之后&#xff0c;选择…

C#开发:PowerDesigner建表和Navicat导入数据

一、打开Powerdesigner&#xff0c;新建一个模型&#xff0c;点击ok 二、用工具面板拖拽出一个数据表 &#xff08;如果没有工具面板&#xff0c;请在如下操作中开启&#xff09; 三、双击刚刚的拖拽出来的表&#xff0c;设计表的字段&#xff0c;可以添加注释说明 【备注】…

《Windwos API每日一练》12.1 剪贴板的简单用法

本节我们讲述剪贴板的简单实现方法。 本节必须掌握的知识点&#xff1a; 剪贴板数据的标准格式 内存分配 把文本传到剪贴板 从剪贴板中取得文本 打开和关闭剪贴板 第76练&#xff1a;剪贴板的简单用法 12.1.1 剪贴板数据的标准格式 Windows支持各种预定义的剪贴板格式&#…

Google Chrome 浏览器在链接上点右键的快捷键

如今&#xff0c;越来越多的软件都懒得设个快捷键&#xff0c;就算设置了连个下划线也懒得加了。 谷歌浏览器右键 > 链接另存为... 和 复制链接地址 的快捷键 (如图)

爬虫自己做的

1.urllib 1.1基本使用 1.2 下载&#xff08;图片&#xff0c;页面&#xff0c;视频&#xff09; 1.3 get 1.3.1 quote 中文变成对应uncode编码 当url 的wd中文时 quote是将中文变成对应uncode编码 然后拼接成完整的url 1.3.2urlencode方法 wd有多个参数 1.3.3ajas get实例 …

Connecting weaviate with langflow across docker containers

题意&#xff1a;在Docker容器之间连接Weaviate与Langflow 问题背景&#xff1a; I am trying to build a local RAG application using Langflow. For my vectore store, I want to use a local Weaviate instance, hosted in a separate docker container on the same netwo…

KAFKA搭建教程

KAFKA搭建教程 期待您的关注 KAFKA学习笔记 帮助更多人 目录 KAFKA搭建教程 1.下载Kafka并解压 2.添加环境变量 3.修改 server.properties 文件 4.将kafka复制到其它节点 5.修改node1、node2节点的broker.id 6.将master的环境变量同步到node1、 node2 7.启动zookeeper…

昇思25天学习打卡营第21天|RNN实现情感分类

Mindspore框架循环神经网络RNN模型实现情感分类 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;一&#xff09;数据集准备 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;二&#xff09;RNN模型 Mindspore框架循环神经网络RNN模型实现情感分类|&…

PostgreSQL 中如何解决因大量并发删除和插入操作导致的索引抖动?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 PostgreSQL 中如何解决因大量并发删除和插入操作导致的索引抖动一、理解索引抖动二、索引抖动的影响三…

nginx通过nginx_upstream_check_module实现后端健康检查

1、简介说明 nginx是常用的反向代理和负载均衡服务&#xff0c;具有强大并发能力、稳定性、丰富的功能集、低资源的消耗。 nginx自身是没有针对后端节点健康检查的&#xff0c;但是可以通过默认自带的ngx_http_proxy_module 模块和ngx_http_upstream_module模块中的相关指令来完…

【Langchain大语言模型开发教程】基于文档问答

&#x1f517; LangChain for LLM Application Development - DeepLearning.AI Embedding&#xff1a; https://huggingface.co/BAAI/bge-large-en-v1.5/tree/main 学习目标 1、Embedding and Vector Store 2、RetrievalQA 引包、加载环境变量 import osfrom dotenv import…

【BUG】已解决:OSError: [Errno 22] Invalid argument

已解决&#xff1a;OSError: [Errno 22] Invalid argument 目录 已解决&#xff1a;OSError: [Errno 22] Invalid argument 【常见模块错误】 错误原因&#xff1a; 解决方法如下&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&…

快速认识EA(Enterprise Architecture)

前言 企业架构&#xff0c;英文是&#xff1a;Enterprise Architecture&#xff0c;简称&#xff1a;EA&#xff0c;是承接企业战略规划与IT建设之间的桥梁&#xff0c;是企业信息化的核心&#xff0c;主要包括业务架构和IT架构。 架构的本质是管理和解决系统的复杂性&#x…

06. 截断文本 选择任何链接 :root 和 html 有什么区别

截断文本 对超过一行的文本进行截断,在末尾添加省略号(…)。 使用 overflow: hidden 防止文本超出其尺寸。使用 white-space: nowrap 防止文本超过一行高度。使用 text-overflow: ellipsis 使得如果文本超出其尺寸,将以省略号结尾。为元素指定固定的 width,以确定何时显示省略…

mysql无法启动

总是报错&#xff1a; 1、Job for mysql.service failed because the control process exited with error code. See "systemctl status mysql.service" and "journalctl -xeu mysql.service" for details. 2、ERROR 2002 (HY000): Cant connect to local …

linux中常见的协议、服务端口整理汇总

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 ​ &#x1f3c5;阿里云ACE认证高级工程师 ​ &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社…

【机器学习实战】数据增强基础

文章目录 1. 数据增强2. 数据增强技巧torchvision2.1 图像大小调整2.2 图像旋转2.3 图像转换为张量2.4 归一化2.5 图像裁剪2.6 仿射变换2.7 透视变换 3. 自动增强4. Mixup增强 1. 数据增强 什么是数据增强&#xff1f;数据增强是优化数据吗&#xff1f;这种说法并不尽然。首先…

Golang | Leetcode Golang题解之第239题滑动窗口最大值

题目&#xff1a; 题解&#xff1a; func maxSlidingWindow(nums []int, k int) []int {n : len(nums)prefixMax : make([]int, n)suffixMax : make([]int, n)for i, v : range nums {if i%k 0 {prefixMax[i] v} else {prefixMax[i] max(prefixMax[i-1], v)}}for i : n - 1…

将github上的项目导入到vscode并创建虚拟环境

1、将github上的项目导入到vscode 直接从github上下载到本地&#xff0c;用vscode打开&#xff08;Open file&#xff09; 2、创建虚拟环境 python -m venv <name> <name>\Scripts\activate ps: 1、退出虚拟环境 deactivate 2、如果运行python -m venv <…

Unity格斗游戏,两个角色之间互相锁定对方,做圆周运动

1&#xff0c;灵感来源 今天手头的工作忙完了&#xff0c;就等着服务器那边完活&#xff0c;于是开始研究同步问题。 正好想到之前想做的&#xff0c;两个小人对线PK&#xff0c;便有了这篇文章。 2&#xff0c;要实现的效果 如图所示&#xff0c;两个小人可以互相锁定&…