C++并发与多线程(五)互斥量,atomic、与线程池(上)

简介: C++并发与多线程(五)互斥量,atomic、与线程池(上)

互斥量


  互斥量多线程编程中 用于保护共享数据:先锁住, 操作共享数据, 解锁。有两个线程,对一个变量进行操作,一个线程读这个变量的值,一个线程往这个变量中写值。即使是一个简单变量的读取和写入操作,如果不加锁,也有可能会导致读写值混乱(一条C语句会被拆成34条汇编语句来执行,所以仍然有可能混乱)。


#include <iostream>
#include <thread>
using namespace std;
int g_count = 0;
void mythread1() {
  for (int i = 0; i < 1000000; i++) {
    g_count++;
  }
}
int main() {
  std::thread t1(mythread1);
  std::thread t2(mythread1);
  t1.join();
  t2.join();
  cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}

  出现上述情况的问题在于g_count++;这步操作被打断了。我们可以使用mutex解决这个问题:


#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int g_count = 0;
std::mutex mymutex;
void mythread1() {
  for (int i = 0; i < 1000000; i++) {
    std::unique_lock<std::mutex> u1(mymutex);
    g_count++;
  }
}
int main() {
  std::thread t1(mythread1);
  std::thread t2(mythread1);
  t1.join();
  t2.join();
  cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}


std::atomic


  大家可以把原子操作理解成一种:不需要用到互斥量加锁(无锁)技术的多线程并发编程方式。原子操作:在多线程中不会被打断的程序执行片段。从效率上来说,原子操作要比互斥量的方式效率要高。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。原子操作,一般都是指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的,不可能出现半完成状态std::atomic来代表原子操作,是个类模板。其实std::atomic是用来封装某个类型的值的。需要添加#include头文件。

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
std::atomic<int> g_count = 0; //封装了一个类型为int的 对象(值)
void mythread1() {
  for (int i = 0; i < 1000000; i++) {
    g_count++;
  }
}
int main() {
  std::thread t1(mythread1);
  std::thread t2(mythread1);
  t1.join();
  t2.join();
  cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}

  输出结果为:


#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
std::atomic<bool> g_ifEnd = false; //封装了一个类型为bool的 对象(值)
void mythread() {
  std::chrono::milliseconds dura(1000);
  while (g_ifEnd == false) {
    cout << "thread id = " << std::this_thread::get_id() << "运行中" << endl;
    std::this_thread::sleep_for(dura);
  }
  cout << "thread id = " << std::this_thread::get_id() << "运行结束" << endl;
}
int main() {
  std::thread t1(mythread);
  std::thread t2(mythread);
  std::chrono::milliseconds dura(5000);
  std::this_thread::sleep_for(dura);
  g_ifEnd = true;
  cout << "程序执行完毕" << endl;
  t1.join();
  t2.join();
}

  程序输出结果为:


  原子操作一般用于计数或者统计(如累计发送多少个数据包,累计接收到了多少个数据包),多个线程一起统计,这种情况如果不使用原子操作会导致统计发生混乱。


std::atomic续谈


#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
std::atomic<int> g_count;
void mythread1() {
    for (int i = 0; i < 1000000; i++) {
        //g_count += 1; // 正确
        g_count = g_count + 1; // 错误:虽然g_count使用了原子操作模板,但是这种写法既读又写,
    }
}
int main() {
    std::thread t1(mythread1);
    std::thread t2(mythread1);
    t1.join();
    t2.join();
    cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}

  一般atomic原子操作,针对+++=-=&=|=^=是支持的,其他操作不一定支持。


std::async深入理解


  std::async参数详述,async用来创建一个异步任务。延迟调用参数 std::launch::deferred【延迟调用】,std::launch::async【强制创建一个线程】。std::async()我们一般不叫创建线程(他能够创建线程),我们一般叫它创建一个异步任务。


#include <iostream>
#include <mutex>
#include <thread>
#include <future>
using namespace std;
std::atomic<int> g_count;
int mythread() {
    cout << "thread start thread id is: " << std::this_thread::get_id() << endl;
    return 10;
}
int main() {
    cout << "main start thread id is: " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(mythread);
    cout << "res.get() is : " << res.get() << endl;
}

  程序输出结果为:

main start thread id is: 0x1000e3d40
res.get() is : thread start thread id is: 0x16fe87000
10

  std::asyncstd::thread最明显的不同,就是async有时候并不创建新线程。

  1. 如果用std::launch::deferred来调用async
#include <iostream>
#include <mutex>
#include <thread>
#include <future>
using namespace std;
std::atomic<int> g_count;
int mythread() {
    cout << "thread start thread id is: " << std::this_thread::get_id() << endl;
    return 10;
}
int main() {
    cout << "main start thread id is: " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(std::launch::deferred,mythread);
    cout << "res.get() is : " << res.get() << endl;
}

  程序输出结果为:

main start thread id is: 0x1000e3d40
res.get() is : thread start thread id is: 0x1000e3d40
10

  延迟到调用get()或者wait()时执行,如果不调用就不会执行。并且没有创建新线程。

相关文章
|
9月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
374 0
|
9月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
10月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
676 5
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
519 20
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
295 1
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
1099 7
|
消息中间件 存储 安全
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
465 5
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
549 0

热门文章

最新文章