Linux多线程基础(2):生产者与消费者模型、POSIX信号量、读写锁、线程池、单例模式

1. 生产者消费者模型

1.1 123原则

1是:1个线程安全的队列

  • 队列特性,先进先出
  • 线程安全,需要保证在同一时刻,队列当中的元素只有一个执行流去访问
  • 互斥锁+条件变量

2是:两种关系的线程

  • 生产线程
  • 消费线程

3是:3种关系

  • 生产者与生产者互斥
  • 消费者与消费者互斥
  • 生产者与消费者同步+互斥
1.2 优点
  • 支持忙闲不均
  • 生产者与消费者解耦开来
  • 支持高并发
1.3 图示

在这里插入图片描述

1.4 实现

思路:

  • 队列:可以借助STL当中的queue

  • 线程安全:STL中的容器都是线程不安全的

    • 互斥:使用互斥锁
    • 同步:使用条件变量
  • 两种角色的线程:

    • 生产者线程:负责往队列当中插入数据
    • 消费者线程:负责出队列消费数据

使用C++ queue模拟阻塞队列的生产消费模型代码:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <queue>
//生产者消费者模型
//1. 一个线程安全的队列
//2. 两个角色的线程  生产线程&消费线程 
//3.3种关系 


//线程安全的队列
class BlockQueue
{
  public:
    BlockQueue(int capacity)
    {
      capacity_ = capacity;
      pthread_mutex_init(&lock_,NULL);
      pthread_cond_init(&prod_cond_,NULL);
      pthread_cond_init(&cons_cond_,NULL);
    }

    ~BlockQueue()
    {
      //销毁
      pthread_mutex_destroy(&lock_);
      pthread_cond_destroy(&prod_cond_);
      pthread_cond_destroy(&cons_cond_);
    }
    //push存放数据
    void Push(int data)
    {
        //加锁
        pthread_mutex_lock(&lock_);
        
        //如果队列已满,则阻塞等待
        while(que_.size()>=capacity_)
        {
          pthread_cond_wait(&prod_cond_,&lock_);
        }
        //插入数据
        que_.push(data);
        //通知消费线程
        pthread_cond_signal(&cons_cond_);
        //解锁
        pthread_mutex_unlock(&lock_);
    }
    //取出数据
    void Pop(int *data)
    {
      
        pthread_mutex_lock(&lock_);
        
        //如果队列为空,则阻塞等待
        while(que_.empty())
        {
          pthread_cond_wait(&cons_cond_,&lock_);
        }
        //出队
        *data = que_.front();
        que_.pop();
        //通知生产线程
        pthread_cond_signal(&prod_cond_);
        //解锁
        pthread_mutex_unlock(&lock_);
    }
  private:
    std::queue<int> que_;
    //容量
    int capacity_;
    //互斥锁
    pthread_mutex_t lock_;
    //条件变量
    pthread_cond_t prod_cond_;
    pthread_cond_t cons_cond_;
};

//定义线程数量
#define THREADCOUNT 1


//生产线程入口函数
void* prodStart(void* arg)
{
  BlockQueue* bq = (BlockQueue*)arg;
  
  int data = 0;
  while(1)
  {
    bq->Push(data);
    printf("i am thread %p,push data:%d\n",pthread_self(),data);
    data++;
  }
  return NULL;
}

//消费线程入口函数
void* consStart(void* arg)
{
  
  BlockQueue* bq = (BlockQueue*)arg;
  int data = 0;
  while(1)
  {
    bq->Pop(&data);
    printf("i am thread %p,pop data:%d\n",pthread_self(),data);
  }
  return NULL;
}
int main()
{
  BlockQueue *blockQueue = new BlockQueue(10);
  //创建线程
  pthread_t prod_tid[THREADCOUNT],cons_tid[THREADCOUNT];
  for(int i = 0;i<THREADCOUNT;i++)
  {

    int ret = pthread_create(&prod_tid[i],NULL,prodStart,(void*)blockQueue);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }

     ret = pthread_create(&cons_tid[i],NULL,consStart,(void*)blockQueue);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }

  }
  //main线程阻塞等待
  for(int i = 0;i<THREADCOUNT;i++)
  {
    pthread_join(prod_tid[i],NULL);
    pthread_join(cons_tid[i],NULL);
  }
  return 0;
}
//编译
g++ $^ -o $@ -g -l pthread

运行结果(部分):
在这里插入图片描述

2. POSIX信号量

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

本质:

  • 计数器+PCB等待队列+一堆接口(等待接口+唤醒接口)

  • 计数器:本质是对资源的计数

    • 当执行流获取信号量成功之后,信号量当中的计数器会进行减1操作,当获取失败后,该执行流就会被放到PCB等待队列当中
    • 当执行流释放信号量成功后,信号量当中的计数器会进行加1操作
2.2 接口
2.2.1 定义
  • sem_t:

    -eg:sem_t sem;

2.2.2 初始化

函数:

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

函数说明:

  • sem:传入信号量的地址

  • pshared:表示当前信号量是使用在进程间还是线程间
    当使用sem_init初始化信号量为进程间的时候,会在内核中创建一块共享内存,来保存信号量的数据结构,其中资源计数器,PCB等待队列都是在共享内存当中维护的,所以我们调用唤醒或者等待接口的时候,就通过操作共享内存实现了不同进程间的通信,进而实现不同进程之间的同步与互斥。

    • 0:表示线程之间
    • 1:表示进程之间
  • value:实际资源数量,用于初始化信号量当中资源计数器的

2.2.3 阻塞等待
  • 如果调用等待接口进行获取信号量,会对资源计数器进行减1操作
  • 如果判断当前信号当中资源计数器的值大于0,则能够成功获取信号量,表示可以访问临界资源,sem_wait函数会返回。
  • 如果资源计数器小于等于0,则调用该接口的执行流被阻塞,该执行流被放到PCB等待队列中。
2.2.3.1 阻塞方式的等待
  • int sem_wait(sem_t *sem)
2.2.3.2 非阻塞方式的等待
  • int sem_trywait(sem_t *sem)
2.2.3.3 带有超时时间的等待
  • int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout)
2.2.4 唤醒

函数:

  • int sem_post(sem_t *sem)

作用:

  • 发布信号量,表示资源使用完成了,需要归还资源或者生产者重新生产了一个资源,对信号量当中的资源计数器进行加1操作。唤醒PCB等待队列当中的PCB
2.2.5 销毁信号量
  • int sem_destroy(sem_t *sem)
2.2.6 实现同步与互斥

互斥:

  • 初始化信号量当中的计数器为1,表示说只有一个资源可以被使用
  • 当执行流A想要访问临界资源时,首先获取信号量,由于计数器中的值为1,表示可以访问,计数器的值从1变成0,从而执行流A去访问临界资源
  • 此时当执行流B想要访问临界资源的时候,获取信号量,但是计数器当中的值为0,表示不能够访问临界资源,执行流B的PCB就被放到了PCB等待队列当中,同时信号量当中的计数器的值减1(0 --> -1),-1表示当前还有1个执行流在等待访问临界资源

同步:

  • 不要求信号量当中的计数器一定为1,也可以为其他正整数

  • 当执行流想要访问临界资源的时候,首先获取信号量

    • 如果信号量当中的计数器大于0,则表示能够访问临界资源,则该执行流不会阻塞,顺序执行临界区代码
    • 如果信号量当中的计数器值小于等于0,则表示不能访问临界资源,则该执行流会被放到PCB等待队列中,同时计数器也会进行减1操作
    • 注意:如果计数器的值为负数,表示当前还有计数器的绝对值个执行流在等待
  • 当释放信号量的时候,会对信号量当中的计数器进行加1操作,是否唤醒PCB等待队列当中的执行流

    • 计数器加1操作之后还为负数或者为0,则需要通知PCB等待队列当中的执行流
    • 计数器加1操作之后为正数,则不需要通知PCB等待队列
2.3 实现

基于环形队列的生产消费模型:环形队列采用数组模拟

代码:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <iostream>
#include <semaphore.h>

/*
 * posix信号量  基于环形队列的生产消费模型
 * 本质 计数器+PCB等待队列+一堆接口  计数器:本质是对资源的计数
 * 1. 对于数组下标,pos = (pos+1)%数组大小 
 * 2. 对读写数组实现线程安全的时候
 *    互斥: sem_t lock; sem_init(&lock,0,1);
 *    同步:
 *        生产者信号量: sem_t prod; sem_init(&prod,0,数组大小);
 *    由于一开始数组当中没有空间可以读,则计数器初始值为0
 *        消费者信号量: sem_t cons; sem_init(&cons,0,0);
 */ 

//定义数组容量大小
#define CAPACITY 4

//线程安全的队列
class RingQueue
{
  public:
    RingQueue()
      :vec_(CAPACITY)
    {
      capacity_ = CAPACITY;
      //初始化信号量
      sem_init(&lock_,0,1);//初始化资源数为1,即只有一个线程在同一时刻能够拥有该信号量
      sem_init(&prod_,0,capacity_);// 信号量计数器的值和数组的空闲空间一样大
      // 在初始化时,由于数组没有一个有效元素,所有初始化资源数为0,后边生产线程在唤醒消费线程的时候,会对消费者信号量当中的计数器进行加加操作,从而消费者线程可以获取到消费信号量,进而去访问数组
      sem_init(&cons_,0,0); 
      //初始化下标位置
      pos_read_ = 0;
      pos_write_ = 0;
      
    }
    ~RingQueue()
    {
      //销毁
      sem_destroy(&lock_);
      sem_destroy(&prod_);
      sem_destroy(&cons_);
    }

    void Push(int data)
    {
      sem_wait(&prod_);

      sem_wait(&lock_);

      vec_[pos_write_] = data;
      pos_write_ = (pos_write_+1)%capacity_;

      sem_post(&lock_);

      sem_post(&cons_);
    }

    void Pop(int* data)
    {
      sem_wait(&cons_);

      sem_wait(&lock_);

      *data = vec_[pos_read_];
      pos_read_ = (pos_read_+1)%capacity_;

      sem_post(&lock_);

      sem_post(&prod_);
    }
  private:
    //数组
    std::vector<int> vec_;
    //数组容量
    int capacity_;
    //信号量
    sem_t lock_; // 锁信号量   互斥
    //同步
    sem_t prod_; // 生产者信号量
    sem_t cons_; // 消费者信号量

    //读写位置 下标
    int pos_write_;
    int pos_read_;
};

#define THREADCOUNT 2

//生产线程入口函数
void* prodStart(void* arg)
{
  RingQueue* rq = (RingQueue*)arg;
  int data = 0;
  while(1)
  {
    rq->Push(data);
    printf("i am prod_thread %p,push data:%d\n",pthread_self(),data);
    data++;
  }
  return NULL;
}

//消费线程入口函数
void* consStart(void* arg)
{
  RingQueue* rq = (RingQueue*)arg;
  int data;
  while(1)
  {
    rq->Pop(&data);
    printf("i am cons_thread %p,pop data:%d\n",pthread_self(),data);
  }
  return NULL;
}
int main()
{
  RingQueue* rq = new RingQueue();
  //创建线程
  pthread_t prod_tid[THREADCOUNT],cons_tid[THREADCOUNT];
  for(int i=0;i<THREADCOUNT;i++)
  {
    int ret = pthread_create(&prod_tid[i],NULL,prodStart,(void*)rq);
    if(ret<0)
    {
      perror("pthread_create");
      return -1;
    }
    ret = pthread_create(&cons_tid[i],NULL,consStart,(void*)rq);
    if(ret<0)
    {
      perror("pthread_create");
      return -1;
    }
  }

  for(int i = 0;i<THREADCOUNT;i++)
  {
    pthread_join(prod_tid[i],NULL);
    pthread_join(cons_tid[i],NULL);
  }

  delete rq;
  rq = NULL;

  return 0;
}
//编译
g++ $^ -o $@ -g -l pthread

运行结果(部分):
在这里插入图片描述

3. 读者写者问题(读写锁)

3.1 背景

在编写多线程时,有一种情况十分常见,那就是大量读,少量写的场景。

  • 读的本质,只是去访问变量的内容,但是并没有修改,最终只是访问变量内容,并没有修改,不会导致程序结果的二义性
  • 对于都是读的线程,即使不加锁,也不会对程序结果造成二义性,基于这种情况,加互斥锁反而会降低程序运行效率。
  • 因此读写锁可以专门处理这种场景,需要注意的是写独占,读共享,写锁优先级高。

机制:

  • 一旦读写锁发现,有执行流想要以写模式打开读写锁,即使后面有想要以读模式打开读写锁的线程,该线程也会阻塞掉
  • 保证想要以写模式打开读写锁的线程一定能够拿到读写锁。

读写锁行为图示:
在这里插入图片描述
读写锁的三种状态:

  • 读模式下的加锁状态
  • 写模式下的加锁状态
  • 不加锁的状态
3.2 读写锁接口
3.2.1 初始化

函数:

  • int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)

函数说明:

  • pthread_rwlock_t:读写锁的类型,一般情况下在使用的时候,都是传入变量的地址
  • pthread_rwlockattr_t :读写锁的属性,一般传递NULL,采用默认属性
3.2.2 销毁

函数:

  • int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
3.2.3 加锁
3.2.3.1 读加锁

函数:

  • int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
    阻塞类型的读加锁接口
  • int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
    非阻塞类型的读加锁接口

函数说明:

  • 最大的用处:允许多个线程以读加锁的方式获取到读写锁
  • 本质上:
    • 读写锁当中维护了一个引用计数,每当线程以读方式获取了读写锁,该引用计数进行++
    • 当释放以读加锁方式的读写锁的时候,会先对引用计数进行–,直到引用计数的值为0的时候,才真正释放了这把读写锁
3.2.3.2 写加锁

函数:

  • int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
    类似于互斥锁,程序当中只允许一个线程,以写方式打开读写锁。
3.2.4 解锁

函数:

  • int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)

函数说明:

  • 不管是以读方式获取的读写锁,还是以写方式获取的读写锁,使用该解锁接口都可以进行解锁
  • 注意:以读方式获取的读写锁,引用计数为0的时候才真正释放了读写锁。
3.2.5 设置读写优先

函数:

  • int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref)

函数说明:

  • attr:读写锁属性

    • 示例:
    • pthread_rwlockattr_t attr:定义读写锁属性
    • pthread_rwlockattr_init(&attr):初始化
  • pref:属性宏定义

    • PTHREAD_RWLOCK_PREFER_READER_NP (默认设置):读者优先,可能会导致写者饥饿情况
    • PTHREAD_RWLOCK_PREFER_WRITER_NP:写者优先,目前有BUG,导致表现行为和读者优先一致。
    • PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP:写者优先,但写者不能递归加锁
3.3 代码示例
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <cstring>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <cstdio>

//读写锁

volatile int ticket = 1000;

//定义读写锁
pthread_rwlock_t rwlock;

//读
void* reader(void* arg)
{
  char* id = (char*)arg;
  while(1)
  {
    //加锁
    pthread_rwlock_rdlock(&rwlock);
    if(ticket<=0)
    {
      //解锁
      pthread_rwlock_unlock(&rwlock);
      break;
    }

    printf("%s: %d\n",id,ticket);
    //解锁
    
    pthread_rwlock_unlock(&rwlock);

    usleep(1);

  }
  return NULL;
}

//写
void* writer(void* arg)
{
  char* id = (char*)arg;
  while(1)
  {
    pthread_rwlock_wrlock(&rwlock);
    if(ticket<=0)
    {
      pthread_rwlock_unlock(&rwlock);
      break;
    }
    printf("%s: %d\n",id,--ticket);
    pthread_rwlock_unlock(&rwlock);

    usleep(1);
  }
  return NULL;
}


struct ThreadAttr
{
  pthread_t tid;
  std::string id;
};

std::string create_reader_id(size_t i)
{
  //利用ostringstream进行string 拼接
  std::ostringstream oss("thread reader ",std::ios_base::ate);
  oss<<i;
  return oss.str();
}

std::string create_writer_id(size_t i)
{
  std::ostringstream oss("thread writer ",std::ios_base::ate);
  oss << i;
  return oss.str();
}

//创建读线程
void init_readers(std::vector<ThreadAttr>& vec)
{
  for(int i = 0;i<vec.size();i++)
  {
    vec[i].id = create_reader_id(i);
    int ret = pthread_create(&vec[i].tid,NULL,reader,(void*)vec[i].id.c_str());
    if(ret < 0)
    {
      perror("pthread_create");
      return;
    }

  }
}

// 创建写线程
void init_writers(std::vector<ThreadAttr>& vec)
{
  for(int i = 0;i<vec.size();i++)
  {
    vec[i].id = create_writer_id(i);
    int ret = pthread_create(&vec[i].tid,NULL,writer,(void*)vec[i].id.c_str());
    if(ret < 0)
    {
      perror("pthread_create");
      return;
    }
  }
}

//回收线程
void join_threads(std::vector<ThreadAttr>const& vec)
{
  //vector的迭代器
  for(std::vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin();it != vec.rend();it++)
  {
    pthread_t const& tid = it->tid;
    pthread_join(tid,NULL);
  }
}

//初始化读写锁
void init_rwlock()
{
#if 0
  //设置写优先
  pthread_rwlockattr_t attr;
  pthread_rwlockattr_init(&attr);
  pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NP);
  pthread_rwlock_init(&rwlock,&attr);
  pthread_rwlockattr_destroy(&attr);
#else
  //默认读优先,会造成写饥饿
  pthread_rwlock_init(&rwlock,NULL);
#endif
}
int main()
{
  //定义写线程数量
  const std::size_t reader_nr = 2000;
  //定义读线程数量
  const std::size_t writer_nr = 2;

  std::vector<ThreadAttr> readers(reader_nr);
  std::vector<ThreadAttr> writers(writer_nr);

  //初始化读写锁
  init_rwlock();
  
  //创建读线程
  init_readers(readers);
  //创建写线程
  init_writers(writers);
  //main阻塞等待回收线程
  join_threads(writers);
  join_threads(readers);
  //销毁读写锁
  pthread_rwlock_destroy(&rwlock);
  return 0;
}

//编译
g++ $^ -o $@ -g -l pthread

运行结果(部分):
在这里插入图片描述
如图,只能看到写饥饿。

4. 线程池

4.1 概念&使用场景

线程池:

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

应用场景:

  • 需要大量的线程来完成任务,且完成任务的时间比较短。
  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
4.2 线程池示例

在这里插入图片描述
线程池中的线程,都是同一种角色的线程,言外之意,指的是每一个线程都执行同样的入口函数。

那么如何让相同入口函数的线程,处理不同的请求:

  • switch case:处理大量不同请求的时候,比较麻烦
  • 向线程池抛入数据的时候,将处理该数据的函数一起抛入(函数地址),线程池当中的线程只需要调用传入的函数处理传入的数据即可

线程处理数据采用的方式:

  • 当从队列中拿出来的数据和处理业务数据混合在一起,如果某一个线程处理数据时间特别漫长,即只有一个线程在处理数据,其他线程都在等待获取队列当中的元素
  • 将从队列当中拿数据和处理业务数据解耦开来,即加锁的时候只需要保证拿数据的时候互斥,在处理业务数据的时候,多个线程可以并行的去运行。

考虑让线程池中的线程退出:

  • 原则:一定是线程池当中的线程安全队列没有数据可以处理了,这会才可以退出线程
  • 灰度升级+负载均衡
4.3 代码示例
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <queue>
#include <iostream>
#include <vector>

//重定义一个返回值为void,参数为int的函数指针类型
typedef void (*Handler)(int);

//数据和处理数据的方式类
class QueueData
{
  public:
    QueueData(int data,Handler handler)
    {
        data_ = data;
        handler_ = handler;
    }
    ~QueueData(){}
    //处理数据
    void Run()
    {
      handler_(data_);
    }
  private:
    int data_;
    Handler handler_;
};

//线程池
class ThreadPool
{
  public:
    ThreadPool(int capacity,int thread_count)
    {
      capacity_ = capacity;
      thread_count_ = thread_count; 
	  //初始化互斥锁
      pthread_mutex_init(&lock_,NULL);
      //初始化条件变量
      pthread_cond_init(&cons_cond_,NULL);

      flag = 0;
    }

    ~ThreadPool() //销毁
    {
      pthread_mutex_destroy(&lock_);
      pthread_cond_destroy(&cons_cond_);
    }
    //创建线程
    int THread_init()
    {
      pthread_t tid;
      for(int i=0;i<thread_count_;i++)
      {
        int ret = pthread_create(&tid,NULL,PoolStart,(void*)this);
        if(ret < 0)
        {
          perror("pthrad_create");
          return -1;
        }
      }
      return 0;
    }
    //往队列中push数据
    int Push(QueueData* qd)
    {
       //加锁
       pthread_mutex_lock(&lock_);
       if(flag)
       {
         //退出前解锁
         pthread_mutex_unlock(&lock_);
         return -1;
       }
       que_.push(qd);
       //解锁
       pthread_mutex_unlock(&lock_);

       //通知消费者线程
       pthread_cond_signal(&cons_cond_);

       return 0;
    }
  private:
    void Pop(QueueData** qd)
    {
      *qd = que_.front();
      que_.pop();

    }

    //线程入口函数
    static void* PoolStart(void* arg)
    {
      //线程分离
      pthread_detach(pthread_self());
      ThreadPool *tp = (ThreadPool*)arg;
      while(1)
      {
        //加锁
        pthread_mutex_lock(&tp->lock_);
        while(tp->que_.empty())
        {
          //等待
          
          // 判断是否退出线程
          if(tp->flag)
          {
            tp->thread_count_--;
            pthread_mutex_unlock(&tp->lock_);
            pthread_exit(NULL);
          }
          //队列为空进入等待
          pthread_cond_wait(&tp->cons_cond_,&tp->lock_);
        }
       //队列中有数据
        QueueData* qd;
        tp->Pop(&qd);
        
        //解锁
        pthread_mutex_unlock(&tp->lock_);
        //处理数据
        qd->Run();
        delete qd;
        
      }

    }
  public:
    //线程退出
    void ThreadExit()
    {
      //加锁
      pthread_mutex_lock(&lock_);
      flag = 1;
      pthread_mutex_unlock(&lock_);
      //通知全部处于等待的线程
      pthread_cond_broadcast(&cons_cond_);
    }
  private:
    //线程安全的队列
    size_t capacity_;//队列大小
    std::queue<QueueData*> que_;
    //互斥锁
    pthread_mutex_t lock_;
    //条件变量
    pthread_cond_t cons_cond_; // 都是消费线程,只需要消费线程的条件变量

    //线程的数量个数
    int thread_count_;
    // 中断标志位 线程退出标志
    int flag;
};

//处理数据的函数
void DealData(int data)
{
  printf("data:%d\n",data);
}

int main()
{
  //创建一个线程池,队列大小为4,线程数量为2
  ThreadPool* tp = new ThreadPool(4,2);
  if(!tp)
  {
    printf("create ThreadPool failed\n");
    return -1;
  }
  
  //创建线程
  int ret = tp->THread_init();
  if(ret<0)
  {
    printf("create ThreadPool failed\n");
    return -1;
  }

  for(int i = 0;i<100;i++)
  {
    //向队列中存放数据
    QueueData* qd = new QueueData(i,DealData);
    if(!qd)
    {
      continue;
    }
    tp->Push(qd);
  }

  sleep(10);//休眠10s后让线程池中的线程退出
  tp->ThreadExit();
  delete tp;
#if 0
  while(1)
  {
    sleep(1);
  }
#endif
  return 0;
}

运行结果:
在这里插入图片描述

5. 线程安全的单例模式

5.1 设计模式

设计模式的优点:

  • 代码复用程度高
  • 程序比较可靠,并且容易理解
  • 代码框架比较稳定

设计模式的分类:

  • 创建型模式:单例模式
  • 结构型模式:适配器模式
  • 行为型模式:观察者模式
5.2 单例模式

特点:

  • 全局提供唯一一个类的实例,具有全局变量的特点

使用场景:

  • 内存池,数据池

基础要点:

  • 全局只有一个实例 ===> static+禁止构造+禁止拷贝构造+禁止赋值拷贝
  • 线程安全
  • 调用者是通过类的函数来获取实例
5.2.1 饿汉模式-线程不安全

程序启动的时候就进行初始化,资源在程序初始化的时候就全部加载完毕了

  • 优点:程序运行速度很快,流畅
  • 缺点:程序初始化的时候耗时比较长

代码示例:

template<class T>
class Singleton
{
  private:
    static T instance;//禁止构造,拷贝构造,赋值拷贝
  public:
    static T* GetInstance()
    {
      return &instance;
    }
};

5.2.2 懒汉模式

资源在使用的时候才进行实例化,单例类的对象在使用的时候才进行实例化

  • 优点:程序初始化的时候比较快
  • 缺点:运行的时候没有饿汉式流畅,线程安全
5.2.2.1 懒汉模式-单线程版:

代码示例:

template<class T>
class Singleton
{
  private:
     static T* instance;
  public:
  static T* GetInstance()
  {
    if(instance==NULL)
    {
      instance = new T();
    }
    return instance;
  }
};

5.2.2.2 懒汉模式-多线程版-性能低:

代码示例:

template<class T>
class Singleton
{
   private:
      static T* instance; 
      static std::mutex lock;  
  public:
    static T* GetInstance()
    {
        //加锁
        lock.lock();
        if(instance == NULL)
        {
        	instance = new T();
        }       
        //解锁
        lock.unlock();
      
      return instance;
    }   
};
5.2.2.3 懒汉模式-多线程版-性能高:

代码示例:

template<class T>
class Singleton
{
   private:
      volatile static T* instance; // volatile 禁止指令重排序
      static std::mutex lock; 
  public:
    static T* GetInstance()
    {
      if(instance==NULL)
      {
        //加锁
        lock.lock();
        if(instance==NULL)    // 双重校验锁 ,降低锁冲突的概率,提高性能
                              //使用互斥锁,保证多线程情况下也只调用一次new
        {
           instance = new T();
        }
        //解锁
        lock.unlock();
      }
      return instance;
    }
    
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值