Linux 12:多线程2

news2024/9/20 8:01:23

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);
  }
  void
      PushData(const int &data)
  {
    LockQueue();
    while (IsFull())
    {
      NotifyConsume();
      std::cout << "queue full, notify consume data, product stop." << std::endl;
      ProductWait();
    }
    q.push(data);
    // NotifyConsume();
    UnLockQueue();
  }
  void
      PopData(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); // P
    q[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.coloradmin.cn/o/1937505.html

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

相关文章

记录|C# winform布局学习

目录 前言一、自适应布局Step1. 添加AutoAdaptWindowsSize类Step2. Form中引用Step3. 创建SizeChanged事件函数Step4. 在Fram.Disiger中添加 更新时间 前言 参考视频&#xff1a; C#5分钟winform快速自适应布局 参考文章&#xff1a; 其他参考&#xff1a; 写这篇文章&#xff…

【python学习】python的知识点总结、特点和思考及解答(代码示例)

引言 python 是一种高级编程语言&#xff0c;具有简洁的语法和丰富的库&#xff0c;被广泛应用于Web开发、数据分析、人工智能、科学计算等领域 文章目录 引言一、python知识点总结1.1 基础语法1.1.1 变量和数据类型1.1.2 控制结构1.1.3 函数和模块 1.2 面向对象编程1.2.1 类和…

vue的三大核心知识点

响应式&#xff1a; 监听data属性getter setter(包括数组)模板编译&#xff1a; 模板到render函数再到vnodevdom&#xff1a; patch(elem, vnode)和patch(vnode, newVnode) vue组件初次渲染过程 解析模板为render函数&#xff08;或在开发环境已完成&#xff0c;vue-loader&a…

Vue 对接海康威视,实现摄像头画面展示

文章目录 需求分析1. 下载2. 安装3. new 一个WebControl 插件相关实例 需求 项目中集成海康威视&#xff0c;实现摄像头画面展示 分析 1. 下载 传送门&#xff1a;官方插件包和文档下载 2. 安装 &#xff08;1&#xff09;下载完成后打开 &#xff08;2&#xff09;在项…

三款知名的基于RAG技术的智能体平台分析

这篇文章是关于目前市面上三款知名的基于检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技术的智能体平台的详细对比分析。这三款产品分别是FastGPT、Dify和Coze。文章从不同维度分析了这些产品的优势和劣势&#xff0c;以供读者参考。 什么是RAG&…

【Qt之·类QRandomGenerator】

系列文章目录 文章目录 前言一、概述1.2. 二、实例演示总结 前言 一、概述 1. 2. 二、实例演示 示例1&#xff1a; #include <QRandomGenerator> #include <QRandomGenerator64> #include <QDebug>int randomInt QRandomGenerator::global()->bound…

ESP8266模块(2)

实例1 查看附近的WiFi 步骤1&#xff1a;进入AT指令模式 使用USB转串口适配器将ESP8266模块连接到电脑。打开串口终端软件&#xff0c;并设置正确的串口和波特率&#xff08;通常为115200&#xff09;。输入以下命令并按回车确认&#xff1a; AT如果模块响应OK&#xff0c;…

TCP状态转换详解

1.什么是TCP的状态转换 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层协议。在 TCP 连接的生命周期中&#xff0c;连接的状态会随着不同阶段的通信而发生变化&#xff0c;这些变化被称为状…

数据库最佳实践:优化爬虫管理的数据存储方案

摘要&#xff1a; 面对日益增长的数据抓取需求&#xff0c;如何高效管理和存储爬虫获取的海量信息成为一大挑战。本文将深入探讨数据库最佳实践&#xff0c;揭示如何通过优化策略提升爬虫数据存储效率&#xff0c;助您跨越数据管理的障碍&#xff0c;实现数据价值最大化。 一、…

虚拟试衣人像合成新SOTA!IMAGDressing-v1:ControlNet和IP-Adapter的最佳拍档

文章链接&#xff1a;https://arxiv.org/pdf/2407.12705 github链接&#xff1a;https://imagdressing.github.io/ Demo试用&#xff1a;https://sf.dictdoc.site/ 亮点直击 为商家引入了一项新的虚拟试衣&#xff08;VD&#xff09;任务&#xff0c;并设计了一个综合亲和力测量…

关闭 Linux 服务器上的 IPv6

虽然 IPv6 已经逐渐普及&#xff0c;但在某些 Linux 服务器上的业务系统仍然可能遇到一些奇怪的问题。特别是在集群场景中&#xff0c;因为集群各个节点之间需要互相通信&#xff0c;如果 IPv6 没有正确配置网络&#xff0c;可能导致一些未知问题&#xff0c;解决起来相当麻烦。…

acwing796-子矩阵的和-前缀和

s矩阵是全局变量&#xff0c;维度n*m,从1~n和 1~m存储元素【0】【0】~【0】【m】和【0】【0】~【n】【0】分别存储的都是0.s矩阵刚开始是存储输入的元素&#xff0c;后面用于存储前缀和。 s矩阵的意思是s【i】【j】表示从【0】【0】到【i】【j】为对角线的矩阵里面所有元素的和…

Pytorch的编译新特性TorchDynamo的工作原理和使用示例

在深度学习中&#xff0c;优化模型性能至关重要&#xff0c;特别是对于需要快速执行和实时推断的应用。而PyTorch在平衡动态图执行与高性能方面常常面临挑战。传统的PyTorch优化技术在处理动态计算图时效果有限&#xff0c;导致训练时间延长和模型性能不佳。TorchDynamo是一种为…

AI批量剪辑,批量发布大模型矩阵系统搭建开发

目录 前言 一、AI矩阵系统功能 二、AI批量剪辑可以解决什么问题&#xff1f; 总结&#xff1a; 前言 基于ai生成或剪辑视频的原理&#xff0c;利用ai将原视频进行混剪&#xff0c;生成新的视频素材。ai会将剪辑好的视频加上标题&#xff0c;批量发布到各个自媒体账号上。这…

[CP_AUTOSAR]_通信服务_CanTp模块(二)

目录 3、功能规范3.1、提供给上层的服务3.1.1、Initialization and shutdown3.1.2、Transmit request3.1.3、Transmit cancellation 3.2、提供给下层的服务3.2.1、Transmit confirmation3.2.2、Reception indication 3.3、内部行为3.3.1、N-SDU接收 在前面 《[CP_AUTOSAR]_通信…

一款异次元小清新风格的响应式wordpress个人博客主题

一款异次元小清新风格的响应式个人博客主题。这是一款专注于用户阅读体验的响应式 WordPress 主题&#xff0c;整体布局简洁大方&#xff0c;针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净&#xff0c;简单且响应迅速的博客主题&#x…

go-微服务的设计概括

一、微服务到底是什么&#xff1f; 初学者很容易把微服务和分布式混为一谈&#xff0c;但其实二者之间存在非常大的差异&#xff0c;我个人认为主要有以下几点&#xff1a; 分布式主要是一种技术手段&#xff0c;用来保证多个相同的进程能够共同工作而不出错。采用各种复杂的…

修复公路 (最小生成树)

//新生训练 Input 4 4 1 2 6 1 3 4 1 4 5 4 2 3 Output 5 #include <iostream> #include <algorithm> #include <bits/stdc.h> using namespace std; typedef long long ll;struct road {int u,v;ll w;bool operator<(const road a)const{return w<a.w…

每日练习*

目录 一、选择题二、知识点1.中间件特点的描述1.1中间件的定义和作用1.2中间件的主要特点1.3中间件的应用场景1.4中间件的发展趋势 二、重写与重载总结![](https://i-blog.csdnimg.cn/direct/aa4190dfbd0e463294e41059016b8895.png) 一、选择题 题目选自牛客网 1.执行下列代码…

自动化测试 - selenium 环境搭建

在进行自动化测试时&#xff0c;Selenium 是一个非常强大的工具&#xff0c;在使用前需要做一些环境准备。 1. 配置 Chromedriver 访问 Chrome 浏览器的官方网站&#xff08;https://www.google.cn/chrome/&#xff09;&#xff0c;下载并安装 Chrome 浏览器。 接下来&#x…