C++ 并发编程实战 第八章 设计并发代码 一
目录
8.1 在线程间切分任务
8.1.1 先在线程间切分数据,再开始处理
8.1.2 以递归方式划分数据
8.1.3 依据工作类别划分任务
借多线程分离关注点需防范两大风险
在线程间按流程划分任务
8.2 影响并发性能的因素
8.2.1 处理器的数量
8.2.2 数据竞争和缓存兵乓
8.2.3 不经意共享
8.2.4 数据的紧凑程度
8.2.5 过度任务切换与线程过饱和
参考:https://github.com/xiaoweiChen/CPP-Concurrency-In-Action-2ed-2019/blob/master/content/chapter8/8.5-chinese.md
8.1 在线程间切分任务
需要决定使用多少个线程,并且这些线程应该去做什么。需要决定是使用“全能”线程去完成所有的任务,还是使用“专业”线程去完成一件事情,或将两种方法混合。使用并发时,需要作出诸多选择来驱动并发,选择会决定代码的性能和可读性。因此选择至关重要,所以设计应用结构时,需要作出适当的决定。本节中,将看到很多划分任务的技术。
8.1.1 先在线程间切分数据,再开始处理
最简单的并行算法就是并行化的std::for_each
,会对数据集中每个元素执行同一个操作。
最简单的分配方式:第一组N个元素分配一个线程,下一组N个元素再分配一个线程,以此类推,如图8.1所示。不管数据怎么分,每个线程都会对分配给它的元素进行操作,但不会和其他线程进行沟通,直到处理完成。
虽然这个技术十分强大,但是并不是哪里都适用。有时不能像之前那样,对任务进行整齐的划分,因为只有对数据进行处理后,才能进行明确的划分。这里的方式特别适用了递归算法,下面就来看看这种特别的方式。
8.1.2 以递归方式划分数据
代码8.1 使用栈的并行快速排序算法——等待数据块排序
template<typename T>
struct sorter // 1
{struct chunk_to_sort{std::list<T> data;std::promise<std::list<T> > promise;};thread_safe_stack<chunk_to_sort> chunks; // 2std::vector<std::thread> threads; // 3unsigned const max_thread_count;std::atomic<bool> end_of_data;sorter():max_thread_count(std::thread::hardware_concurrency()-1),end_of_data(false){}~sorter() // 4{end_of_data=true; // 5for(unsigned i=0;i<threads.size();++i){threads[i].join(); // 6}}void try_sort_chunk(){boost::shared_ptr<chunk_to_sort > chunk=chunks.pop(); // 7if(chunk){sort_chunk(chunk); // 8}}std::list<T> do_sort(std::list<T>& chunk_data) // 9{if(chunk_data.empty()){return chunk_data;}std::list<T> result;result.splice(result.begin(),chunk_data,chunk_data.begin());T const& partition_val=*result.begin();typename std::list<T>::iterator divide_point= // 10std::partition(chunk_data.begin(),chunk_data.end(),[&](T const& val){return val<partition_val;});chunk_to_sort new_lower_chunk;new_lower_chunk.data.splice(new_lower_chunk.data.end(),chunk_data,chunk_data.begin(),divide_point);std::future<std::list<T> > new_lower=new_lower_chunk.promise.get_future();chunks.push(std::move(new_lower_chunk)); // 11if(threads.size()<max_thread_count) // 12{threads.push_back(std::thread(&sorter<T>::sort_thread,this));}std::list<T> new_higher(do_sort(chunk_data));result.splice(result.end(),new_higher);while(new_lower.wait_for(std::chrono::seconds(0)) !=std::future_status::ready) // 13{try_sort_chunk(); // 14}result.splice(result.begin(),new_lower.get());return result;}void sort_chunk(boost::shared_ptr<chunk_to_sort> const& chunk){chunk->promise.set_value(do_sort(chunk->data)); // 15}void sort_thread(){while(!end_of_data) // 16{try_sort_chunk(); // 17std::this_thread::yield(); // 18}}
};template<typename T>
std::list<T> parallel_quick_sort(std::list<T> input) // 19
{if(input.empty()){return input;}sorter<T> s;return s.do_sort(input); // 20
}
这个方案使用到了特殊的线程池——所有线程任务都来源于一个等待链表,然后线程会去完成任务,任务完成后会再来链表提取任务。这个线程池会出问题(包括对工作链表的竞争),问题的解决方案将在第9章提到。关于多处理器的问题,将会在本章后面的章节中做出更为详细的介绍(详见8.2.1)。
任务几种划分方法:处理前划分和递归划分(都需要事先知道数据的长度固定),还有上面的划分方式。事情并非总是这样好解决,当数据是动态生成或是通过外部输入,那这里的办法就不适用了。这种情况下,基于任务类型的划分方式,就要好于基于数据的划分方式。
8.1.3 依据工作类别划分任务
虽然会为每个线程分配不同的数据块,因为这里每个线程对每个数据块的操作是相同的,所以工作的划分(无论是之前就划分好,还是使用递归的方式划分)仍停留在理论阶段。另一种选择是让线程做专门的工作,就是每个线程做不同的工作,就像水管工和电工在建造一所屋子的时候,所做的不同工作那样。线程可能会对同一段数据进行操作,但对数据进行不同的操作。
对分工的排序,也就是分离关注点。每个线程都有不同的任务,这意味着真正意义上的线程独立。其他线程偶尔会向特定线程交付数据,或是通过触发事件的方式来进行处理。不过总体而言,每个线程只需要关注自己所要做的事情即可。其本身就是良好的设计,每一段代码只对自己的部分负责。
借多线程分离关注点需防范两大风险
多线程下有两个危险需要分离。第一个是对错误的担忧(主要表现为线程间共享着很多的数据),第二是不同的线程要相互等待,这两种情况都是因为线程间很密切的交互。这种情况发生时,就需要看一下为什么需要这么多交互。当所有交互都有同样的问题,就应该使用单线程来解决,并将引用同一源的线程提取出来。或者当有两个线程需要频繁的交流,在没有其他线程时,就可以将这两个线程合为一个线程。
当通过任务类型对线程间的任务进行划分时,不应该让线程处于隔离态。当多个输入数据集需要使用同样的操作序列,可以将序列中的操作分成多个阶段让线程执行。
在线程间按流程划分任务
当任务会应用到相同操作序列,去处理独立的数据项时,就可以使用流水线(pipeline)系统进行并发。这好比一个物理管道:数据流从管道一端进入,进行一系列操作后,从管道另一端出去。
使用这种方式划分工作,可以为流水线中的每一阶段操作创建一个独立线程。当一个操作完成,数据元素会放在队列中,供下一阶段的线程使用。这就允许第一个线程在完成对于第一个数据块的操作时,第二个线程可以对第一个数据块执行管线中的第二个操作。
这就是线程间划分数据的一种替代方案(如8.1.1描述),这种方式适合于操作开始前,且对输入数据处长度不清楚的情况。例如:数据来源可能是从网络,或者可能是通过扫描文件系统来确定要处理的文件。
流水线对于队列中的耗时操作处理也很合理,通过对线程间任务的划分,就能对应用的性能有所改善。假设有20个数据项,需要在四核的机器上处理,并且每一个数据项需要四个步骤来完成操作,每一步都需要3秒来完成。如果将数据分给了四个线程,每个线程上就有5个数据项要处理。假设在处理时,没有其他线程对处理过程进行影响,在12秒后4个数据项处理完成,24秒后8个数据项处理完成,以此类推。当20个数据项都完成操作,就需要1分钟的时间。管线中就会完全不同,四步可以交给四个内核,第一个数据项可以被每一个核进行处理,所以其还是会消耗12秒。在12秒后你就能得到一个处理过的数据项,这相较于数据划分并没有好多少。不过,当流水线动起来,事情就会不一样了。第一个核处理第一个数据项后,数据项就会交给下一个内核,所以第一个核在处理完第一个数据项后,其还可以对第二个数据项进行处理。在12秒后,每3秒将会得到一个已处理的数据项,这就要好于每隔12秒完成4个数据项。
8.2 影响并发性能的因素
多处理系统中使用并发来提高代码的效率时,需要了解影响并发的效率的因素。即使使用多线程对关注点进行分离,还需要确定是否会对性能造成负面影响
8.2.1 处理器的数量
处理器数量是影响多线程应用的首要因素。当对目标硬件很熟悉,并且能针对硬件进行软件设计,并在目标系统或副本上进行性能测试。那很幸运,可以在类似的平台上进行开发。不过,当所使用的平台与目标平台的差异很大,比如:可能会在一个双芯或四芯的系统上做开发,而用户系统可能就只有单个处理器(可能有很多芯),或多个单芯处理器,亦或是多核多芯的处理器。并发程序在不同平台上的行为和性能特点可能完全不同,需要在不同平台上进行测试。
为了扩展线程的数量,且与硬件所支持的并发线程数一致,C++标准线程库提供std::thread::hardware_concurrency()
,使用这个函数就能知道在给定硬件上可以扩展的线程数量了。
使用std::thread::hardware_concurrency()
需要谨慎,因为不会考虑其他应用已使用的线程数量(除非已经将系统信息进行共享)。std::async()
可以避免这个问题,标准库会对所有调用进行安排。同样,谨慎的使用线程池也可以避免这个问题。
随着处理器数量的增加,另一个问题就会来影响性能:多个处理器尝试访问同一个数据。
8.2.2 数据竞争和缓存兵乓
当两个线程在不同处理器上时,对同一数据进行读取,通常不会出现问题。因为数据会拷贝到每个线程的缓存中,并让两个处理器同时进行处理。当有线程对数据进行修改,并且需要更新到其他核芯的缓存中去,就要耗费一定的时间。这样的修改可能会让第二个处理器停下来,等待硬件内存更新缓存中的数据。根据CPU指令,这是一个特别特别慢的操作。
思考下面的代码段:
std::atomic<unsigned long> counter(0);
void processing_loop()
{while(counter.fetch_add(1,std::memory_order_relaxed)<100000000){do_something();}
}
counter变量是全局的,任何线程都能调用processing_loop()。因此,每次对counter进行增量操作时,处理器必须确保缓存中的counter是最新值,然后进行修改,再告知其他处理器。编译器不会为任何数据做同步操作,fetch_add是一个“读-改-写”操作,因此要对最新的值进行检索。如果另一个线程在另一个处理器上执行同样的代码,counter的数据需要在两个处理器之间进行传递,这两个处理器的缓存中间就存有counter的最新值(当counter的值增加时)。如果do_something()足够短,或有很多处理器来对这段代码进行处理时,处理器会互相等待。一个处理器准备更新这个值,另一个处理器在修改这个值,所以该处理器就需要等待第二个处理器更新完成,并且完成更新传递时才能执行更新,这种情况被称为高竞争(high contention)。如果处理器很少需要互相等待就是低竞争(low contention)。
循环中counter的数据将在每个缓存中传递若干次,这就是乒乓缓存(cache ping-pong),这会对应用的性能有着重大的影响。当处理器因为等待缓存转移而停止运行时,这个处理器就不能做任何事情,所以对于整个应用来说这是一个坏消息。
如何避免乒乓缓存呢?答案就是:减少两个线程对同一个内存位置的竞争。
虽然,要实现起来并不简单。即使给定内存位置,因为伪共享(false sharing)可能还是会有乒乓缓存。
8.2.3 不经意共享
处理器缓存通常不会用来处理单个存储点,而是会用来处理称为缓存行(cache lines)的内存块。内存块通常大小为32或64字节,实际大小需要由处理器来决定。因为硬件缓存可确定处理缓存行的大小,较小的数据项就在同一内存行的相邻位置上。有时这样的设定还不错:当线程访问的一组数据是在同一数据行中,对于应用的性能来说就要好于对多个缓存行进行传播。不过,同一缓存行存储的是无关数据时,且需要被不同线程访问,这就会造成性能问题。
假设一个int类型的数组,并且有一组线程可以访问数组中的元素,且对数组的访问很频繁。通常int的大小要小于一个缓存行,同一个缓存行中可以存储多个数据项。因此,即使每个线程都能对数据中的成员进行访问,硬件还是会产生乒乓缓存。每当线程访问0号数据项,并对其值进行更新时,缓存行的所有权就需要转移给执行该线程的处理器,这仅是为了更新1号数据项的线程获取1号线程的所有权。缓存行是共享的(即使没有数据存在),因此使用伪共享来描述这种方式。这个问题的解决办法就是对数据进行构造,让同一线程访问的数据项存在临近的地址中(就像是放在同一缓存行中),这样那些能被独立线程访问的数据将分布在相距很远的地方,并且可能是存储在不同的缓存行中。本章接下来的内容中可以看到,这种思路对代码和数据设计的影响。C++17标准在头文件<new>
中定义了std::hardware_destructive_interference_size
它指定了当前编译目标可能共享的连续字节的最大数目。如果确保数据间隔大于等于这个字节数,就不会有错误的共享存在了。
8.2.4 数据的紧凑程度
伪共享(不经意共享)发生的原因:某个线程所要访问的数据过于接近另一线程的数据,另一个是与数据布局相关的陷阱会直接影响单线程的性能。问题在于数据过于接近:单线程访问数据时,数据就已在内存中展开,分布在不同的缓存行上。另一方面,当内存中有紧凑数据时,数据就分布在同一缓存行上。因此,当数据已传播,将会有更多的缓存行从处理器的缓存上加载数据,这会增加访问内存的延迟,以及降低数据的性能(与紧凑的数据存储地址相比较)。
同样的,如果数据已传播,给定缓存行上就包含与当前线程有关和无关的数据。极端情况下,有更多的数据存在于缓存中,就会对数据以更多的关注,而非这些数据去做了什么。这就会浪费宝贵的缓存空间,增加处理器缓存缺失的情况,从而因为其他数据已经占有缓存中的位置,所以需要再从主存中添加对应数据项到缓存中。
现在,对于单线程代码来说任务切换(task switching)就很关键了。如果系统中的线程数量要比核芯多,每个核上都要运行多个线程。这就会增加缓存的压力,为了避免伪共享,努力让不同线程访问不同缓存行。当处理器切换线程时,要对不同内存行上的数据进行加载(当不同线程使用的数据跨越了多个缓存行时),而非对缓存中的数据保持原样(当线程中的数据都在同一缓存行时)。C++17在头文件<new>
中指定了一个常数std::hardware_constructive_interference_size
,这是同一高速缓存行上的连续字节的最大数目(需要对齐)。将所需的数据大小控制在这个字节数内,就能提高缓存命中率。
如果线程数量多于内核或处理器数量,操作系统可能会选择将线程安排给这个核芯一段时间,之后再安排给另一个核芯一段时间。就需要将缓存行从一个内核上,转移到另一个内核上,也意味着要耗费很多时间。虽然,操作系统通常会避免这样的情况发生,不过当其发生的时候,对性能会有很大影响。
当有超级多的线程准备运行时(非等待状态),任务切换就会频繁发生。这个问题我们之前也接触过:超额申请。
8.2.5 过度任务切换与线程过饱和
多线程系统中,通常线程的数量要多于处理器的数量,除非在大规模并行(massively parallel)硬件上运行。不过,线程会花费时间来等待外部I/O完成,或被互斥量阻塞,或等待条件变量等等,所以等待不是问题。使用额外的线程来完成有用的工作,而非让线程在处理器处以闲置状态时持续等待。
不过,这也并非长久之计,如果有很多额外线程,就会有很多线程准备执行。不过,当线程数远远大于可用处理器数量时,操作系统就会忙于切换任务,以确保每个任务都有时间运行,这将增加切换任务的时间开销,和缓存问题造成同一结果。当无限制的产生新线程,超额申请就会加剧,或者在通过任务类型对任务进行划分的时候,线程数量大于处理器数量。这时,对性能影响的因素是CPU的能力,而非I/O。
如果只是简单的通过数据划分生成多个线程,可以限定工作线程的数量,如8.1.2节中那样。如果超额申请是对工作的划分而产生,那么不同的划分方式对性能就没有太多益处了。
其他因素也会影响多线程代码的性能,即使CPU类型和时钟周期相同,乒乓缓存的开销也可以让程序在两个单核处理器和在一个双核处理器上,产生不明显的性能差。
相关文章:
C++ 并发编程实战 第八章 设计并发代码 一
目录 8.1 在线程间切分任务 8.1.1 先在线程间切分数据,再开始处理 8.1.2 以递归方式划分数据 8.1.3 依据工作类别划分任务 借多线程分离关注点需防范两大风险 在线程间按流程划分任务 8.2 影响并发性能的因素 8.2.1 处理器的数量 8.2.2 数据竞争和缓存兵乓…...
设计模式8、装饰者模式 Decorator
解释说明:动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案 抽象构件(Component):定义一个抽象接口以规范准备收附加责任的对象 具体构件(ConcreteCom…...
抖音开放平台第三方代小程序开发,一整套流程
大家好,我是小悟 抖音小程序第三方平台开发着力于解决抖音生态体系内的小程序管理问题,一套模板,随处部署。能尽可能地减少服务商的开发成本,服务商只用开发一套小程序代码作为模板就可以快速批量的孵化出大量的商家小程序。 第…...
Flutter笔记:滚动之-无限滚动与动态加载的实现(GetX简单状态管理版)
Flutter笔记 无限滚动与动态加载的实现(GeX简单状态管理版) 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq…...
前端架构师之02_ES6_高级
1 类和继承 1.1 class类 JavaScript 语言中,生成实例对象的传统方法是通过构造函数。 // ES5 创建对象 // 创建一个类,用户名 密码 function User(name,pass){// 添加属性this.name name;this.pass pass; } // 用 原型 添加方法 User.prototype.sho…...
VScode多文件编译/调试配置
之前都是在Visual Studio写C/C,最近想换到VScode,折腾半天把launch.json和tasks.json配好了(虽然不懂为什么,但确实能用了),在此做个记录。 参考资料:1,2,3 环境&#…...
K折交叉验证——cross_val_score函数使用说明
在机器学习中,许多算法中多个超参数,超参数的取值不同会导致结果差异很大,如何确定最优的超参数?此时就需要进行交叉验证的方法,sklearn给我们提供了相应的cross_val_score函数,可对数据集进行交叉验证划分…...
2023.09.30使用golang1.18编译Hel10-Web/Databasetools的windows版
#Go 1.21新增的 log/slog 完美解决了以上问题,并且带来了很多其他很实用的特性。 本次编译不使用log/slog 包 su - echo $GOPATH ;echo $GOROOT; cd /tmp; busybox wget --no-check-certificate https://go.dev/dl/go1.18.linux-amd64.tar.gz;\ which tar&&am…...
React简介
react作为前端主流框架之一,因其语法接近原生JavaScript语法而广受欢迎。其生态丰富,常用的就有react-router、react-redux等插件,还有与其匹配的UI组件库antd。而且其还有用于移动端开发的react-native库,因此,react值…...
链表经典面试题(一)
面试题 1.反转链表的题目2.反转链表的图文分析3.反转链表的代码实现 1.反转链表的题目 2.反转链表的图文分析 我们在实现反转链表的时候,是将后面的元素变前面,前面的元素变后面,那么我们是否可以理解为,用头插法的思想来完成反转链表呢&…...
体验亚马逊的 CodeWhisperer 感觉
CodeWhisperer 是亚马逊推出的辅助编程工具,在程序员写代码时,它能根据其内容生成多种代码建议。 CodeWhisperer 目前已支持近10几种语言,我是用 java 语言,用的开发工具是 idea,说一下我用的情况。 亚马逊云科技开发…...
6、行内元素和块元素
6、行内元素和块元素 一、块元素 无论内容多少,该元素独占一行 如p标签、标题标签(h1-h6…) 二、行内元素 内容撑开宽度、左右都是行内元素的可以排在一行 一些元素如果能够摆放在一行都可以用行内元素,但是如果需要换行就需…...
LeetCode 面试题 08.01. 三步问题
文章目录 一、题目二、Java 题解 一、题目 三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。 示例1: 输入&…...
[CSCCTF 2019 Qual]FlaskLight 过滤 url_for globals 绕过globals过滤
目录 subprocess.Popen FILE warnings.catch_warnings site._Printer 这题很明显就是 SSTI了 源代码 我们试试看 {{7*7}} 然后我们就开始吧 原本我的想法是直接{{url_for.__globals__}} 但是回显是直接500 猜测过滤 我们正常来吧 {{"".__class__}} 查看当前…...
1分钟快速实现Redis数据对比
在上篇「Redis高效、安全的不停机数据迁移方案」的文章中,介绍了NineData在Redis迁移场景下的性能和优势。因为数据在主备、多云和多区域环境之间的迁移流动,难免会产生数据一致性的问题,而结构与数据不一致往往是导致故障的原因之一。所以&a…...
ASUS华硕天选4笔记本电脑FX507VV原厂Windows11系统
下载链接:https://pan.baidu.com/s/1W9tedHI3iFjaHju5eLkQ6g?pwd8dl2 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、华硕电脑管家、奥创控制中心等预装程序 由于时间关系,绝大部分资料没有上传,不是想要的型号,请联系客服获取。...
Vue3配置路由
文章目录 一、创建index.js二、main.js的配置三、在App.vue中引入 一、创建index.js 在src文件夹中创建router文件夹,并在其中创建index.js文件 //引入路由对象 import { createRouter,createWebHistory } from vue-router import PufMac from "../views/puf…...
力扣 -- 97. 交错字符串
解题步骤: 参考代码: class Solution { public:bool isInterleave(string s1, string s2, string s3) {int ms1.size();int ns2.size();//先判断s1的长度s2的长度是否等于s3的长度,如果不等,则s1和s2不可能拼接成s3if(mn!s3.size…...
【剑指Offer】4.二维数组中的查找
题目 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该…...
独立按键控制LED亮灭、独立按键控制LED状态、独立按键控制LED显示二进制、独立按键控制LED移位——“51单片机”
各位CSDN的uu们你们好呀,今天依旧是小雅兰的51单片机的内容,内容主要是:独立按键控制LED亮灭、独立按键控制LED状态、独立按键控制LED显示二进制、独立按键控制LED移位,下面,让我们进入51单片机的世界吧!&a…...
chrome extensions mv3通过content scripts注入/获取原网站的window数据
开发插件的都知道插件的content scripts和top window只共享Dom不共享window和其他数据,如果想拿挂载在window的数据还有点难度,下面会通过事件的方式传递cs和top window之间的数据写一个例子 代码 manifest.json 这里只搞了2个js,content.…...
震坤行API接口聚合解析,实现根据ID取商品详情
震坤行是一个工业品服务平台,提供了API接口供开发者使用。要根据ID获取商品详情,您需要使用震坤行API接口并进行相应的请求。 以下是使用震坤行API接口根据ID获取商品详情的示例代码(使用Python编写): import reques…...
mencpy和strcpy的区别?
今天刷题时遇到了这个问题,记录一下。 strcpy比较简单,就是拷贝字符串,遇到\0时结束拷贝。 memcpy用来做内存拷贝,可以拷贝任何数据类型的对象并指定拷贝数据的长度:char a[100],b[50]; memcpy(b, a, sizeof(b)); 总结…...
机器人过程自动化(RPA)入门 8. 异常处理、调试和日志记录
有时,自动化程序可能无法执行。为了处理此类情况,我们使用异常处理活动。在本章中,我们将从UiPath中可用的各种类型的异常处理方法、您可能遇到的异常以及如何处理它们开始。我们还将学习日志记录。本章涉及的一个重要主题是调试,以检查工作流是否正常工作,并更正任何错误…...
tomcat总结笔记
文章目录 Tomcat1、什么是tomcat?2、rpm安装jre环境3、源码安装jdk环境4、安装tomcat --源码安装5、相关目录文件webappsTomcat 配置文件目录介绍(conf)tomcat的相关管理命令在$prefix/bin目录下 实战1、:配置nginx动静分离实战2、配置基于t…...
中断向量控制器(NVIC)
1. 什么是中断 在处理器中,中断是一个过程,即CPU在正常执行程序的过程中,遇到外部/内部的紧急事件需要处理,暂时中止当前程序的执行,转而去为处理紧急的事件,待处理完毕后再返回被打断的程序处继续往下执行…...
QT配置FFmpeg出现错误原因
文章目录 QT配置ffmpeg出现: undefined reference to "avcodec_version"没有配置环境变量QT和FFmpeg的版本不对应直接添加FFmpeg的头文件没有在.pro文件添加路径 QT 程序异常退出没有在debug文件里面存放dll库 QT配置ffmpeg出现: undefined re…...
列出使用Typescript的一些优点?
使用Typescript有以下优点: 类型安全:Typescript是一种静态类型语言,它要求在编码阶段明确定义变量和函数的类型。这种类型安全可以减少在运行时出现错误的可能性,并提高代码的可读性和可维护性。代码可读性和可维护性࿱…...
如何做好测试?(四)集成测试(Integration Testing, IT)
1. 集成测试的详细介绍: 集成测试(Integration Testing, IT),是一种软件测试方法,旨在验证不同组件、模块或子系统之间的交互和集成是否正常工作。它侧重于测试系统的组件之间的接口和数据传递,以确保它们…...
二叉树前序、中序、后序遍历(递归法、迭代法)
前序遍历:(练习题) 迭代法一: int TreeSize(struct TreeNode* root){return rootNULL?0:TreeSize(root->left)TreeSize(root->right)1; }int* preorderTraversal(struct TreeNode* root, int* returnSize){if(rootNULL){*…...
国内网站服务器/关键词优化包年推广
毫无疑问,Kubernetes已经成为容器领域当之无愧的事实标准。除了Google、Microsoft等技术巨擘们在容器领域里多年的博弈外,国内的BAT、滴滴、蚂蚁、今日头条等技术大厂,也都已将容器和Kubernetes列入未来的战略重心,无数中小型企业…...
个人虚拟机做网站/互联网营销是干什么
property:提供成员变量的访问方法的声明、控制成员变量的访问权限、控制多线程时成员变量的访问环境 。property不但可以在interface,在协议protocol.和类别category中也可以使用。synthesize 合成访问器方法?实现property所声明的方法的定义…...
中山做展示型网站/自己怎么优化网站
关于vagrant,维基百科给出了定义:“Vagrant is an open-source software product for building and maintaining portable virtual development environments.”,看到了吧,是一个搭建虚拟开发环境的开源软件。下面就来看看vagrant…...
网站建设常识/写软文赚钱的平台都有哪些
1.应用场景 主要用于了解前端开发的历史变迁以及从中学习到了什么,将来前端开发的趋势走向 从而更好地面对前端开发~ 2.学习/操作 1.文档阅读 https://blog.csdn.net/csdnnews/article/details/90745990 // 前端开发 20 年变迁史 https://blog.csdn.ne…...
做网站的顶部图片/长沙百度快速优化
原文:http://taligarsiel.com/Projects/howbrowserswork1.htm 有点长,前面介绍的比较细,但是到后面就越写越糊了,两年了 都没有继续写下去,据说是没有时间写了。。。 翻完了之后 想找些 这个作者的资料 无意发现三个…...
app与移动网站开发资料/全网营销老婆第一人
项目五 猜数游戏-循环程序设计;【项目要求】;【项目分析】;问题情境及实现;问题情境及实现;所需知识;1 循环结构的几种形式(1);1 循环结构的几种形式(2);1 循环结构的几种形式(3);1 循环结构的几种形式(4);1 循环结构的几种形式(5);1 循环结构的几种形式(6);1 循环结构的几种形…...