当前位置: 首页> 娱乐> 影视 > 深圳公司排名前十名_嵌入式软件开发工程师培训_智能优化网站_在线培训管理系统

深圳公司排名前十名_嵌入式软件开发工程师培训_智能优化网站_在线培训管理系统

时间:2025/8/9 7:26:51来源:https://blog.csdn.net/m0_74795952/article/details/142904733 浏览次数:1次
深圳公司排名前十名_嵌入式软件开发工程师培训_智能优化网站_在线培训管理系统

四、c++11内容汇总、多线程应用实践

文章目录

    • 四、c++11内容汇总、多线程应用实践
      • 4.1 c++11内容汇总
      • 4.2 C++语言级别的多线程编程
        • 1.通过thread类编写C++多线程程序
          • 1.头文件以及命名空间
          • 2.线程创建
          • 3.子线程如何结束
          • 4.主线程如何处理子线程
        • 2.线程互斥
          • 1.为什么需要线程互斥
          • 2.mutex 互斥锁
          • 3.基于CAS操作的atomic原子类型
        • 3.线程同步通信
          • 条件变量
          • 线程间通信

4.1 c++11内容汇总

一:关键字和语法
auto:可以根据右值,推导出右值的类型,然后左边变量的类型也就已知了
nullptr:给指针专用(能够和整数进行区别)
foreach:可以遍历数组,容器等
for(Type val:container)=>底层就是通过指针或者迭代器来实现的
{}
右值引用:move移动语义函数和forward类型完美转发函数
模板的一个新特性:typename ... A 表示可变参(类型参数)二:绑定器和函数对象
function:函数对象
bind:绑定器
lambda表达式三:智能指针
shared ptr和weak_ptr四:容器
set和map:红黑树 O(1gn)
unordered set和unordered map:哈希表 O(1)
array: 数组
forward list:前向链表五:C++语言级别支持的多线程编程
createThread
pthread create
clone

读者可参考此文章进行学习

C++11 新特性 学习笔记-CSDN博客

4.2 C++语言级别的多线程编程

语言级别多线程最大的优点:可跨平台

就是在语言层面加了一层封装,c++里面加了一些宏可以识别当前的操作系统,然后还是去调用相应的系统调用

比如linux就还是底层调用pthread_creat之类的函数

多线程编程两个问题:

1.线程间的互斥

竞态条件–>临界区代码段–>原子操作–>互斥锁或者CAS

竞态条件:

竞态条件指的是设备或系统出现不恰当的执行时序,导致得到不正确的结果。从多进程间通信的角度来看,竞态条件发生在两个或多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序。在并发编程中,竞态条件通常指的是程序的执行结果依赖于线程执行的顺序。

临界区代码段:

导致竞态条件发生的代码段被称为临界区代码段。在临界区内,代码的执行顺序对结果有重要影响,因此必须确保同一时间只有一个线程能够执行临界区内的代码。

保证临界区代码段的原子操作通过互斥锁或者CAS实现

2.线程间的同步通信

生产者,消费者线程模型

注:C++的STL容器在默认状态下都是线程不安全的

1.通过thread类编写C++多线程程序
1.头文件以及命名空间
头文件:#include<thread>
写using namespace std; 或者 std::thread 都可以使用thread类
2.线程创建

std :: thread定义一个线程对象,传入线程所需要的线程函数和参数,线程自动开启

定义一个线程对象 参数传入一个线程函数threadHandle1

一个线程对应一个线程栈 这个名字就是线程的入口函数

传入后,新线程就开始运行了,其实就是入口函数开始运行了

第一个参数是线程函数,第二个参数开始就是线程函数的参数了

void threadHandle1(int time)
{//让子线程睡眠两秒 一堆命名空间不用管this_thread::sleep_for(std::chrono::seconds(time));cout << "hello thread" << endl;
}thread t1(threadHandle1,2);
3.子线程如何结束

子线程函数运行完成,线程就结束了

4.主线程如何处理子线程

主线程和子线程没有先后顺序,自己运行自己的

子线程可以比主线程早结束,但通常不推荐这么做,最好就是确保子线程都结束了主线程再结束

1.join函数

主线程阻塞等待子线程t1结束,主线程继续往下进行

t1.join();

2.detach函数

使用detach()会将子线程t1与主线程main分离,主线程结束,整个进程结束,所有子线程都自动结束了

不是很安全,不推荐使用

t1.detach();

实例代码:

#include<iostream>
#include<vector>
#include<functional>
#include<algorithm>
#include<ctime>
#include<typeinfo>
#include<thread>
using namespace std;void threadHandle1(int time)
{//让子线程睡眠两秒this_thread::sleep_for(std::chrono::seconds(time));cout << "hello thread" << endl;
}int main()
{thread t1(threadHandle1,2);//t1.join();t1.detach();cout << "main thread" << endl;return 0;
}
2.线程互斥
1.为什么需要线程互斥

竞态条件:多线程程序执行的结果是一直的,不会随着CPU对线程不同的调用顺序而产生不同的运行结果

#include<iostream>
#include<thread>
#include<list>
#include<mutex>
using namespace std;//车站有100张车票,由三个窗口一起卖票
int countSum = 100;//对车票--不是一个线程安全的操作void sellTicket(int index)
{//如果是if的话 就卖三张票就结束了while (countSum > 0){cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;//模拟卖票花的时间 100毫秒this_thread::sleep_for(std::chrono::milliseconds(100));}
}int main()
{list<thread> tlist;for (int i = 1; i <= 3; i++)tlist.push_back(thread(sellTicket, i));for (thread & c : tlist)c.join();cout << "所有窗口卖票结束" << endl;return 0;
}

在本个例子中存在竞态条件,因为每次执行结果都不一样的

原因是,对车票–操作不是一个线程安全的操作,每个线程减到一半可能时间片到了就去执行另外一个线程,导致一张票卖出去多次(出现了相同的数字的票)

2.mutex 互斥锁

在linux中mutex底层也调用的是系统调用,用的是pthread_mutex_t互斥锁

锁的概念参考这篇文章,这里不再赘述

黑马程序员 | linux系统编程 | 学习笔记_linux网络操作系统项目教程黑马程序员电子版-CSDN博客

头文件:

#include<mutex>

互斥锁创建及使用

std::mutex mtx;void 对应线程函数(形参列表)
{mtx.lock();核心代码,需要原子操作的(即必须要一次执行完的,不能执行到一半去别的线程)mtx.unlock();
}
mutex mtx;//全局的一把互斥锁void sellTicket(int index)
{mtx.lock();while(countSum > 0){cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;//模拟卖票花的时间 100毫秒this_thread::sleep_for(std::chrono::milliseconds(100));}mtx.unlock();
}

这样改是不行的,我们把整个函数代码都上锁,那么只会有一个窗口卖票剩下的都不会卖的,因为它占有的mutex资源一直没有释放

所以只保证核心的代码的原子性就行,锁的内容尽量的少

void sellTicket(int index)
{while(countSum > 0){mtx.lock();cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;mtx.unlock();//模拟卖票花的时间 100毫秒this_thread::sleep_for(std::chrono::milliseconds(100));}
}

这样还是不完全安全,如果线程1在卖最后一张票,拿上锁,但还没有–的时候线程2进来了,他一看还有一张票,就等着锁资源,1卖完了最后一张,释放锁,2进来了卖的就是第0张票

所以要采用 锁+双重判断 的模式

void sellTicket(int index)
{while(countSum > 0){mtx.lock();if (countSum > 0){cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;}mtx.unlock();//模拟卖票花的时间 100毫秒this_thread::sleep_for(std::chrono::milliseconds(100));}
}

如果说,mutex中间满足条件子线程函数结束了,那么后面的线程会一直拿不到锁

在C++中,互斥锁(std::mutex)不会自动因为线程的结束而被释放;锁的释放必须显式地通过调用unlock成员函数来完成,或者使用RAII(Resource Acquisition Is Initialization)机制来自动管理。

如果子线程在没有显式释放锁的情况下结束,那么锁将保持锁定状态,这通常会导致几个问题:

  1. 死锁:其他尝试获取该锁的线程将被永久阻塞,因为它们无法获取到已经被持有且未释放的锁。
  2. 资源泄露:锁是一种有限资源,如果它们被永久占用而不释放,那么系统将无法有效地管理这些资源,可能导致性能下降或资源耗尽。
  3. 数据不一致:如果锁保护的共享数据在锁未释放的情况下被其他线程访问或修改,那么可能会导致数据不一致或损坏。

所以要用lock_guard(RALL)来对锁进行管理,类似于裸指针和智能指针那样的

mutex mtx;
lock_guard<mutex> lock(mtx);
需要上锁的核心代码
lock生命周期结束调用析构,自动释放锁资源
void sellTicket(int index)
{while(countSum > 0){//lock_guard对象的作用域是这个while循环,出去以后就调用析构,析构里面实现了释放锁的逻辑lock_guard<mutex> lock(mtx);if (countSum > 0){cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;}//模拟卖票花的时间 100毫秒this_thread::sleep_for(std::chrono::milliseconds(100));}
}

可以通过加大括号来限制lock_guard的作用域,除了大括号就析构释放资源了

void sellTicket(int index)
{while(countSum > 0){//lock_guard对象的作用域是这个while循环,出去以后就调用析构,析构里面实现了释放锁的逻辑{lock_guard<mutex> lock(mtx);if (countSum > 0){cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;}}//模拟卖票花的时间 100毫秒this_thread::sleep_for(std::chrono::milliseconds(100));}
}

但函数参数传递或者返回的时候就不能用lock_guard了,因为它不支持赋值和重载

可以用unique_lock(升级版),和unique_ptr一样,拷贝和赋值重载被delete,但是给了右值引用赋值,调用析构也会释放锁资源

使用方法:

构造和析构时,占有锁和释放锁

也可以显示调用

l.lock()和l.unlock()但是没什么必要

void sellTicket(int index)
{while (countSum > 0){{unique_lock<mutex> l(mtx);if (countSum > 0){cout << "窗口:" << index << "卖出第:" << countSum << "张票" << endl;countSum--;}}this_thread::sleep_for(std::chrono::milliseconds(100));}
}
3.基于CAS操作的atomic原子类型

如果只有一两个语句需要原子特性,那没必要加锁,太麻烦了

系统理论:CAS来保证上面的++ --操作的原子特性就足够了,也被称为无锁操作

这个无锁指的,不是从软件层面加锁,而是硬件层面。

硬件实现具体过程:CPU从内存取出数据,计算,再写回内存的过程中对总线加锁,这个时候不允许线程去使用总线,通过这种方式来完成的"加锁"

面经当中的无锁操作比如无锁队列都指的是通过CAS来实现的

头文件

#include<atomic>

volatile关键字

//加上volatile关键字那么各个线程不再缓存这两个变量,读的都是内存里面原来的那一份
//好处是主线程一旦更改这两个,子线程里面都能读取到
//防止多线程对共享变量进行缓存
volatile atomic_bool isReady = false;
volatile std::atomic_int num = 0;

实例代码:

#include<iostream>
#include<thread>
#include<list>
#include<mutex>
#include<atomic>
using namespace std;volatile atomic_bool isReady = false;
volatile std::atomic_int num = 0;void task(int index)
{while (!isReady){//线程让出当前CPU时间片,等待下一次调度this_thread::yield();}for (int i = 0; i < 100; i++)num++;
}//10线程都对num++100次
int main()
{list<thread> tlist;for (int i = 1; i <= 10; i++)tlist.push_back(thread(task, i));//主线程睡两秒this_thread::sleep_for(std::chrono::seconds(2));isReady = true;cout << num << endl;for (thread & c : tlist)c.join();cout << num << endl;return 0;
}//结果
//0 1000
3.线程同步通信

读者可参考这篇文章的线程同步通信一起学习,是在linux环境下的线程同步通信

黑马程序员 | linux系统编程 | 学习笔记_linux网络操作系统项目教程黑马程序员电子版-CSDN博客

以及下面这篇

【C++】多线程编程图文详解(多角度详解,小白一看就懂!!)-CSDN博客

条件变量

头文件

#include<condition_variable>

std::condition_variable 是C++标准库中的一个类,用于在多线程编程中实现线程间的条件变量和线程同步。它提供了等待通知的机制,使得线程可以等待某个条件成立时被唤醒,或者在满足某个条件时通知其他等待的线程。其提供了以下几个函数用于等待和通知线程:

方法说明
wait使当前线程进入等待状态,直到被其他线程通过**notify_one()notify_all()**函数唤醒。该函数需要一个互斥锁作为参数,调用时会自动释放互斥锁,并在被唤醒后重新获取互斥锁。
wait_for使当前线程进入等待状态,最多等待一定的时间,直到被其他线程通过notify_one()notify_all()函数唤醒,或者等待超时。该函数需要一个互斥锁和一个时间段作为参数,返回时有两种情况:等待超时返回std::cv_status::timeout,被唤醒返回std::cv_status::no_timeout
wait_until使当前线程进入等待状态,直到被其他线程通过notify_one()或notify_all()函数唤醒,或者等待时间达到指定的绝对时间点。该函数需要一个互斥锁和一个绝对时间点作为参数,返回时有两种情况:时间到达返回std::cv_status::timeout,被唤醒返回std::cv_status::no_timeout
notify_one唤醒一个等待中的线程,如果有多个线程在等待,则选择其中一个线程唤醒
notify_all唤醒所有等待中的线程,使它们从等待状态返回

std::condition_variable的主要特点如下:

  • 等待和通知机制:std::condition_variable 允许线程进入等待状态,直到某个条件满足时才被唤醒。线程可以调用wait函数进入等待状态,并指定一个互斥量作为参数,以确保线程在等待期间互斥量被锁定。当其他线程满足条件并调用 notify_onenotify_all 函数时,等待的线程将被唤醒并继续执行。
  • 与互斥量配合使用:std::condition_variable 需要与互斥量(std::mutex或std::unique_lockstd::mutex)配合使用,以确保线程之间的互斥性。在等待之前,线程必须先锁定互斥量,以避免竞争条件。当条件满足时,通知其他等待的线程之前,必须再次锁定互斥量。
  • 支持超时等待:std::condition_variable提供了带有超时参数的等待函数 wait_forwait_until,允许线程在等待一段时间后自动被唤醒。这对于处理超时情况或限时等待非常有用。

使用std::condition_variable的一般步骤如下:

  1. 创建一个std::condition_variable对象。
  2. 创建一个互斥量对象(std::mutex或std::unique_lockstd::mutex)。
  3. 在等待线程中,使用std::unique_lock锁定互斥量,并调用wait函数进入等待状态。
  4. 在唤醒线程中,使用std::unique_lock锁定互斥量,并调用notify_one或notify_all函数通知等待的线程。
  5. 等待线程被唤醒后,继续执行相应的操作。

示例:

模拟一个简单的计数器。一个线程负责增加计数,另一个线程等待并打印计数的值。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
// 定义共享变量和相关的同步工具
int count = 0; // 计数器
std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
// 增加计数的线程函数
void increment() {for (int i = 0; i < 5; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作std::unique_lock<std::mutex> lock(mtx); // 使用 unique_lockcount++; // 增加计数std::cout << "计数增加到: " << count << std::endl;cv.notify_one(); // 通知其他线程}
}
// 打印计数的线程函数
void print() {for (int i = 0; i < 5; ++i) {std::unique_lock<std::mutex> lock(mtx); // 加锁cv.wait(lock); // 等待通知std::cout << "当前计数是: " << count << std::endl; // 打印计数}
}int main() {std::thread t1(increment); // 创建增加计数的线程std::thread t2(print); // 创建打印计数的线程t1.join(); // 等待线程完成t2.join();return 0;
}

共享变量

  • int count = 0;:定义一个共享的计数器。
  • std::mutex mtx;:定义一个互斥锁,用于保护共享变量 count
  • std::condition_variable cv;:定义一个条件变量,用于线程同步。

增加计数的线程 (increment 函数):

  • 使用 std::this_thread::sleep_for 模拟工作,增加计数器的值。
  • 使用 std::lock_guard 加锁,以确保在修改 count 时没有其他线程干扰。
  • 增加计数并打印当前值,然后使用 cv.notify_one() 通知等待的线程。

打印计数的线程 (print 函数):

  • 使用 cv.wait(lock) 等待通知,只有当 increment 函数通知时才会继续执行。
  • 打印当前的计数值。
线程间通信

通过信号量实现线程间的通信(即互相通知)

生产者消费者模型

#include<iostream>
#include<thread>
#include<list>
#include<mutex>
#include<atomic>
#include<queue>
#include<condition_variable>
using namespace std;mutex mtx;//定义互斥锁
condition_variable cv;//定义条件变量,做线程间的同步通信操作class Queue
{
public://生产物品void put(int val){//这个一定要加上,因为put和get是在不同的线程里面调用的,一个生产者调用一个消费者调用unique_lock<mutex> lck(mtx);while (!que.empty()){//que不为空,生产者应该通知消费者去消费,消费完了,再继续生产//生产者线程应该进入等待状态,并且把mtx互斥锁释放掉//阻塞等待的时候应该先把mtx释放掉,不然消费者也没法消费cv.wait(lck);//一进入等待状态就把锁释放掉了}que.push(val);//通知其他所有线程,已经生产好了物品,去消费吧cv.notify_all();/*cv.notify_all:通知其他所有线程cv.notify_one:通知另外的一个线程其他线程得到该通知,就会从等待状态变为阻塞态,还得拿到锁以后才可以继续执行*/cout << "生产者 生产:" << val << "号物品" << endl;}//消费物品int get(){unique_lock<mutex> lck(mtx);while (que.empty()){//消费者发现没有东西可以消费,通知生产者生产cv.wait(lck);//一进入等待状态就把锁释放掉了}int val = que.front();que.pop();cv.notify_all();//通知其他线程(生产者),我消费完了,赶紧生产cout << "消费者 消费" << val << "号物品" << endl;return val;}
private:queue<int> que;
};//生产者线程
void producer(Queue* que)
{for (int i = 1; i <= 10; i++){que->put(i);this_thread::sleep_for(std::chrono::milliseconds(100));}
}
//消费者线程
void consumer(Queue* que)
{for (int i = 1; i <= 10; i++){int val = que->get();this_thread::sleep_for(std::chrono::milliseconds(100));}
}int main()
{Queue que;//传入两者共享的队列thread t1(producer, &que);thread t2(consumer, &que);t1.join();t2.join();return 0;
}

多线程编程部分还可以参考以下博客内容进行学习
https://blog.csdn.net/sjc_0910/article/details/118861539
https://blog.csdn.net/weixin_45754224/article/details/141139153?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-141139153-blog-118861539.235%5Ev43%5Epc_blog_bottom_relevance_base1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-141139153-blog-118861539.235%5Ev43%5Epc_blog_bottom_relevance_base1&utm_relevant_index=5

关键字:深圳公司排名前十名_嵌入式软件开发工程师培训_智能优化网站_在线培训管理系统

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: