【C++11多线程】多线程之数据共享

简介: 【C++11多线程】多线程之数据共享

前言

本节课的主要内容是解决线程中数据共享的问题


提示:以下是本篇文章正文内容,下面案例可供参考

一、数据共享的定义以及示例问题

数据共享的定义:在多个线程中读/写一个变量。

那我们首先要知道:变量能同时读取一个数据但不能同时写和读或者一起写同一个数据。

数据我们可以定义为全局变量或类中的一个成员。

二、解决方案_互斥量、lock()、unlock()

引导

功能为:myin()加数据到list中,myou()输出myin加入的数据

myin()功能为写,myou为读

大家可以试一下自己实现:

1、mutex互斥量

互斥量是上什么:如果不需要信号量的计数能力,有时可以使用信号量的一个简化版本,称为互斥量(mutex)

简单来说就是用来锁住一段代码,其他线程再次锁时就需要等待线程解锁

他在C++中头文件为:#include <mutex>

他是一个类

他的定义为:mutex m;

2、mutex::lock()锁

作用:用来锁住一段代码,其他线程使用时就需要等待解锁。

可以有效的缓解同时读/又读又写的线程

mutex m;
m.lock();//如果没有及时解锁,则:锁到程序结束,并且其他人拿不到锁
........
cout<<"hello world"<<endl;

3、mutex::unlock()解锁

作用:用来解锁一段代码,其他线程可以在次锁这个互斥量。

锁的意义:不让变量同时读写

4、范例演示

讲解:我们有2个线程,他们又读又写,所以我们需要锁住变量以保证数据安全。

我们使用完数据后也需要解锁,要不然其他线程会卡到lock()永远执行不到

class A
{
public:
  //功能为:myin()加数据到li中,myou()输出myin加入的数据
  void myin()
  {
    for (int i = 0; i < 100000; i++)
    {
      cout << "插入数据:" << i << endl;
      m.lock();//加锁保护数据安全
      li.push_back(i);
      m.unlock();//使用完后需要及时解锁
    }
  }
  void myou()
  {
    for (int i = 0; i < 100000; i++)
    {
      m.lock();//加锁保护数据安全
      if (!li.empty())//不为空时输出数据并删除他
      {
        //输出数据
        cout << "数据输出:" << li.front() << endl;
        li.pop_front();
      }
      else//为空则取不到
        cout << "队列为空" << endl;
      m.unlock();//使用完后需要及时解锁
    }
  }
  //定义一个list队列
  list<int> li;
  //定义一个互斥量
  mutex m;
};
int main()
{
  A a;
  //创建2个线程
  thread t(&A::myin);
  thread th(&A::myou);
  //让主线程等待
  t.join();
  th.join();
  return 0;
}


现在就可以稳定地跑起来了。

三、lock_gurad类模板、死锁及解决方案

1、为什么要使用lock_gurad

我相信,有些人lock()后总会忘记unlock()导致其他线程执行不到,卡在lock()那

怎么办呢?系统/C++给我们提供了一个类模板,使用他就不用unlock()了

2、lock_gurad的使用

他其实就是一个类模板

他的作用是不用unlock锁了,他自动unlock()

改一下刚刚的范例:

void myin()
  {
    for (int i = 0; i < 100000; i++)
    {
      cout << "插入数据:" << i << endl;
      lock_guard<mutex> lo(m);
      li.push_back(i);
    }
  }
  void myou()
  {
    for (int i = 0; i < 100000; i++)
    {
      lock_guard<mutex> lo(m);
      if (!li.empty())//不为空时输出数据并删除他
      {
        //输出数据
        cout << "数据输出:" << li.front() << endl;
        li.pop_front();
      }
      else
        cout << "队列为空" << endl;
    }
  }

他还是能正常运行,所以我们写的没有问题

他还有一个功能:变成unlock()

在lock_gurad构造函数参数2写adopt_lock

拓展知识std::lock:一次锁住多个mutex互斥量:

实际:类模板

mutex _1;
mutex _2;
std::lock<mutex>(_1,_2);

3、死锁情况的演示

大概情况讲解:线程A拿到mu1锁,在拿mu2锁,然后解锁mu2,解锁mu1 线程B呢:拿mu2锁,再拿mu1,然后解锁mu1,解锁mu2。这就导致有可能A,B都不解锁。卡在那:

解决方案:锁和解锁两个线程顺序要一样

四、unique_lock lock_gurad加强版

1、与lock_gurad的对比

为什么说unique_lock是 lock_gurad的加强版呢?

答:unique_lock用法更灵活,更多的参数,更高的效率

2、使用

unique_lock也是一个类模板

基本功能和lock_gurad一样一样的,那他灵活在哪呢?

成员函数,和构造函数的参数1多种多样:

构造函数参数1:

1).try_to_lock

没有拿到锁立马返回,立马unlock

怎么看自己有没有拿到锁呢

使用owns_lock函数

unique_lock<mutex> uni(m);
if(uni.owns_lock)
{
  cout<<"拿到了锁"<<endl;
  //干其他的事情
}
else
  cout<<"没有拿到锁"

2).defer_lock

初始化一个mutex

前提:此mutex没有加锁


成员函数:

1).lock()

手动lock()


2).unlock()

手动unlock()

unique_lock可以自己加解锁


3).release()放弃mutex的所以权,并返回mutex的指针

所有权指的是:放弃他的操作,简单的说:调用了release函数就不能操作那个绑定的mutex


相关文章
|
2月前
|
消息中间件 监控 Java
线程池关闭时未完成的任务如何保证数据的一致性?
保证线程池关闭时未完成任务的数据一致性需要综合运用多种方法和机制。通过备份与恢复、事务管理、任务状态记录与恢复、数据同步与协调、错误处理与补偿、监控与预警等手段的结合,以及结合具体业务场景进行分析和制定策略,能够最大程度地确保数据的一致性,保障系统的稳定运行和业务的顺利开展。同时,不断地优化和改进这些方法和机制,也是提高系统性能和可靠性的重要途径。
125 62
|
13天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
40 1
|
2月前
|
消息中间件 存储 安全
|
3月前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
41 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
28 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
45 2
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
52 1
|
3月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
57 1
|
2月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
70 0