C++多线程并发
文章目录
- C++多线程并发
- std::chrono
- C++中的多线程:std::thread
- 主线程等待子线程结束:join
- 主线程分离子线程:detach
- 异步:std::async
- 异步的另一种用法:std::launch::deferred
- std::async的底层实现:std::promise
- std::future的tip
- 互斥量
- std::mutex 上锁
- std::lock_guard
- std::unique_lock
- try_lock
- 死锁
- 读写锁:shared_mutex
- 条件变量(信号量)
- 等待某个条件成真
- 案例
- 原子操作
C++多线程并发
std::chrono
C++11开始引入时间标准库
- 利用C++强类型的特点,明确区分时间点和时间段,明确区分不同的时间单位。
// 时间点的例子:2022年1月8日 13点07分10秒
// 时间段的例子:1分30秒
// 时间点类型:chrono::steady_clock::time_point
// 时间段类型:chrono::milliseconds, chrono::seconds, chrono::munutes等
// 方便的元素安抚重载:时间点+时间段=时间点,时间点-时间点=时间段
auto t0 = chrono::steady_clock::now();
auto t1 = t0 + chrono::seconds(30);
auto dt = t1 - t0;
int64_t sec = chrono::duration_cast<chrnon::seconds>(dt).count()
- 跨平台的sleep:std::this_thread::sleep_for
可以使用std::this_thread::sleep_for
替换unix的usleep。他可以让当前线程休眠一段时间,然后继续
而且单位也可以自己指定,比如milliseconds表示毫秒,也可以换成是microseconds表示微妙,seconds表示秒,chrono的强类型让单位选择更自由
std::this_thread::sleep_for(chrono::milliseconds(1000));
- 为什么需要多线程:无阻塞多任务
我们的程序常常需要同时处理多个任务:例如后台执行一个很耗时的任务,比如下载一个文件,同时还要和用户交互。在GUI应用程序中很常见,比如浏览器在后台下载文件的同时,用户仍然可以用鼠标操作其GUI界面
C++中的多线程:std::thread
- C++11开始,为多线程提供了语言级别的支持。他用std::thread这个类来表示线程
- std::thread构造函数的参数可以是任意的lambda表达式
- 当那个线程启动的时候,就会执行这个lambda表达式里面的内容
- 这样就可以一边和用户交互,一边在另外一个线程里面慢吞吞的下载文件了
在cmake中为了跨平台引入线程包,cmake引入了自己的线程包
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
主线程等待子线程结束:join
如果我们遇到子线程还没有执行完成的时候,主线程就退出的情况,我们则不要着急推出主线程,要让主线程等待子线程结束再退出。我们可以使用std::thread()类的成员函数join()来等待进程的结束。
主线程分离子线程:detach
std::thread的析构函数会销毁线程。作为一个C++类,std::thread同样遵循着RAII思想和三五法则:因为管理着资源,他自定义了析构函数,删除了拷贝构造/赋值构造,但是提供了移动构造/赋值函数
thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}thread& operator=(thread&& _Other) noexcept {if (joinable()) {_STD terminate();}_Thr = _STD exchange(_Other._Thr, {});return *this;}thread(const thread&) = delete;thread& operator=(const thread&) = delete;void swap(thread& _Other) noexcept {_STD swap(_Thr, _Other._Thr);}
因此当thread所在的函数退出的时候,就会调用thread的析构函数,这会销毁该线程这个时候我们调thread::detach()函数,分离该线程–意味着线程的生命周期不再由std::thread对象管理,而是在线程退出以后自动销毁自己。不过进程退出的时候,线程还是会自动退出。
异步:std::async
- std::async是一个接受带有返回值lambda自身返回一个std::future对象
- lambda的函数体可以在另一个线程里面执行
- 接下来你可以在main里面做一些彼得事情,你可以在线程函数内执行别的操作
- 最后调用future的get方法,如果此时线程函数未执行完成,会等待执行完成,并且获取对应的返回值
#include <iostream>
#include <chrono>
#include <thread>
#include <future>
using namespace std;
int test_function(int a)
{std::this_thread::sleep_for(chrono::seconds(a));return a;
}
void test()
{std::cout << "test" << std::endl;
}
int main() {std::future<int> fret = std::async([&](){return test_function(10);});test();// fret.wait();int ret = fret.get();std::cout << "ret:" << ret << std::endl;return 0;
}
当然除了get会等待线程执行完毕之外,wait也可以等待线程执行完成,但是不会返回其值
只要线程没有执行完成,wait会一直等下去,而使用wait_for则可以指定一个最长的等待时间,用chrono里的类表示单位。他会返回一个std::future_status表示等待是否成功。如果超过这个等待时间线程还没有执行完毕,则放弃等待,返回future_status::timeout。
如果线程在指定时间内执行完毕,则认为等待成功,返回future_status::ready。同理还有wait_until其参数是一个时间点。
异步的另一种用法:std::launch::deferred
std::async的第一个参数可以设置成std::launch::deferred,这个时候不会创建一个线程来执行,他只会把lambda函数体内的运算推迟到future的get被调用时。这种写法,执行函数仍然在主线程中执行,他只是函数式变成范式意义上的异步,而不涉及到真正的多线程。可以用这个实现惰性求值之类的需求。
std::async的底层实现:std::promise
如果不想让std::async帮你自动创建线程,想要手动创建线程,可以使用std::promise。然后再线程返回的时候,
用set_value设置返回值。再主线程中是哟个get_future获取future对象,进一步get可以等待并且获取线程的返回值。
#include <iostream>
#include <chrono>
#include <thread>
#include <future>using namespace std;int test_function(int a)
{std::this_thread::sleep_for(chrono::seconds(a));return a;
}void test()
{std::cout << "test" << std::endl;
}int main() {std::promise<int> pret;std::thread t1([&](){auto ret = test_function(5);pret.set_value(ret);});std::future<int> fret = pret.get_future();test();int ret = fret.get();std::cout << "ret:" << ret << std::endl;t1.join();return 0;
}
std::future的tip
future
为了三五法则,阐述了拷贝构造/赋值函数。如果需要浅拷贝,实现共享同一个future对象,可以使用std::shared_future
。
如果不需要返回值,std::async
里面的lambda
返回类型可以设置魏void
,future
对象的类型也设置成std::future<void>
。
同理有std::promise<void>
,他的set_value()
不接受参数,仅仅作为同步用,不传递任何实际的值
互斥量
多线程打架问题:两个线程同时往一个vector中推数据,程序崩溃了,这是一位vector不是一个线程安全的容器,多个线程同时访问同一个vector会出现数据竞争的现象。
#include <iostream>
#include <chrono>
#include <thread>
#include <future>using namespace std;vector<int> aa;int main() {std::thread t1([&](){while(true){aa.push_back(1);std::this_thread::sleep_for(std::chrono::microseconds(0));}});std::thread t2([&](){while(true){aa.push_back(2);std::this_thread::sleep_for(std::chrono::microseconds(0));}});t1.join();t2.join();return 0;
}
std::mutex 上锁
调用std::mutex的lock()时,会检测mutex是否已经上锁,如果没有锁定,则上锁,如果已经锁定,则陷入等待,直到mutex被另一个线程解锁后才再次上锁。而调用unlock则会进行解锁操作。这样就可以保证mtx.lock和mtx.unlock之间的代码段,同一时间只有一个线程在执行,从而避免数据竞争。
std::lock_guard
std::lock_guard是一个在构造的时候调用mtx.lock(),在析构的时候自动调用mtx.unlock()。从而退出函数作用域的时候能够自动解锁,避免程序员不小心忘记解锁
std::unique_lock
std::lock_guard严格在析构的时候unlock(),但是有的时候我们会希望提前unlock()。这个时候就可以std::unique_lock,它额外存储了一个flag表示是否已经释放。他会在结构的时候检测这个flag,如果没有释放,则调用unlock(),否则不调用。
然后可以直接调用unique_lock的unlock(),函数来提前解锁,但是即使忘记解锁也没有关系,退出作用域的时候他还会自动检查一遍是否需要解锁
std::unique_lock的构造函数还可以有一个额外的参数,那就是std::defer_lock
指定了这个参数的话,std::unique_lock不会在构造函数中调用mtx.lock(),需要在之后手动调用lock()函数才能上锁,好处依然是忘记unlock(),也能自动调用unlock()。
try_lock
lock()函数会等待到mutex对象到解锁状态,我们也可以使用try_lock(),在上锁失败的时候不会等待,而是直接返回false,如果上锁成功则返回true
其中try_lock_for表示等待时间,需要使用std::time_mutex ,同理还是又try_lock_until()。
std::unique_lock可以使用std::try_to_lock做参数,和无参数相比,他会调用mtx.try_lock而不是mtx.lock()。之后可以使用grd.owns_lock()判断是否上锁成功。如果使用的参数是std::adopt_lock做参数,则表示mtx是已经上锁的。
死锁
解决办法:
-
最简单的是一个线程永远不要持有两个锁,分别上锁,同样也可以避免死锁
-
保证双方上锁顺序一致
-
如果没有办法保证上锁顺序一致,可以试用贴std::lock(mtx1, mtx2, …)函数,一次性地对多个mutex对象上锁。他接受任意多个mutex作为参数,并且他保证在无论任意线程中调用的顺序是否相同,都不会产生死锁问题。
-
与std::lock_guard相对应,std::lock也有std::scoped_lock。只不过他可以同时对多个mutex上锁。
std::scoped_lock(mtx1, mtx2, ...)
-
如果是自己锁自己的话,可以使用std::recursive_mutex来保证一个线程lock多次lock同一个锁的时候不会产生死锁
读写锁:shared_mutex
条件变量(信号量)
std::condition_variable cv;
cv.wait(lock)将会让当前线程陷入等待
在其他线程中调用cv.notify_one()则会唤醒一个正在等待的线程。
可以发现std::condition_variable必须和一个std::unique_lock一起用。
int main()
{condition_variable ccv;mutex mtx;std::thread t1([&]{unique_lock lck(mtx);ccv.wait(lck);std::cout << "t1 is wait" << std::endl;});std::this_thread::sleep_for(std::chrono::milliseconds(400));std::cout << "notify......" << std::endl;ccv.notify_one();t1.join();return 0;
}
等待某个条件成真
还可以给wait指定一个额外的参数,变成wait(lck, expr) 的形式,其中expr是一个lambda表达式,只有返回值为true时才会真正的唤醒,否则继续等待。
int main()
{condition_variable ccv;mutex mtx;bool ready = false;std::thread t1([&]{unique_lock lck(mtx);ccv.wait(lck, [&](){return ready;});std::cout << "t1 is wait" << std::endl;});std::this_thread::sleep_for(std::chrono::milliseconds(400));std::cout << "notify1......" << std::endl;ccv.notify_one();ready = true;std::cout << "notify2......" << std::endl;ccv.notify_one();t1.join();return 0;
}
如果有多个等待者,可以使用notify_all()来唤醒所有的等待线程,这就是为啥wait需要一个unique_lock作为参数,因为要保证多个线程被唤醒时候,只有一个能被启动。如果不需要,在wait返回之后调用lck.unlock()即可。顺便一提,wait的过程中会暂时unlock这个锁
std::condition_variable只支持std::unique_lock作为wait参数,如果需要其他类型的mutex锁,可以使用std::condition_variable_any
还有wait_for和wait_until函数,分别接收chrono时间段和时间点作为参数
案例
生产者消费者模式
类似于消费队列
#include <string>
#include <deque>
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <vector>
using namespace std;
int main()
{condition_variable ccv;mutex mtx;vector<int> datas;std::thread t1([&]{for (int i = 0; i < 2; i++){unique_lock lck(mtx);ccv.wait(lck, [&](){return datas.size() != 0;});auto it = datas.back();datas.pop_back();lck.unlock();std::cout << "t1 is wait " << it << std::endl;}});std::thread t2([&]{for (int i = 0; i < 2; i++){unique_lock lck(mtx);ccv.wait(lck, [&](){return datas.size() != 0;});auto it = datas.back();datas.pop_back();lck.unlock();std::cout << "t2 is wait " << it << std::endl;}});datas.push_back(1);ccv.notify_one();datas.push_back(2);ccv.notify_one();datas.push_back(3);datas.push_back(4);ccv.notify_all();t1.join();t2.join();return 0;
}
原子操作
atomic 有专门的硬件指令加持
cpu识别到该指令的时候会锁住内存总线,放弃乱序执行等优化策略(将该指令视为一个同步点,强制同步掉之前所有的内存操作),从而向你保证该操作是原子的,不会执行到中途另外一个线程插一脚进来。
对于程序员,只需要把int修改成atomic<int>
即可,不需要像mutex那样需要手动上锁解锁,因此使用起来也比较直观
int main()
{condition_variable ccv;std::atomic<int> counter = 0;std::thread t1([&]{for (int i = 0; i < 10000; i++){// counter = counter+1;counter++;}});std::thread t2([&]{for (int i = 0; i < 10000; i++){// counter = counter+1;counter++;}});t1.join();t2.join();std::cout << counter << std::endl;return 0;
}
注意:注释起来的代码不能保证执行的原子性
除了使用运算符重载,还可以使用
// fetch_add: 和+=等价
// store: 和=等价
// load: 读取对应的值
// exchange(val)会把val写入原子变量,并返回old值
// compare_exchange_strong 读取,比较是否相等,不相等则写入
相关文章:
C++多线程并发
文章目录 C多线程并发std::chronoC中的多线程:std::thread主线程等待子线程结束:join主线程分离子线程:detach异步:std::async异步的另一种用法:std::launch::deferredstd::async的底层实现:std::promisest…...
新火种AI|摊上事儿了!13名OpenAI与谷歌员工联合发声:AI失控可能导致人类灭绝...
作者:小岩 编辑:彩云 2024年,OpenAI的CEO Sam Altman就没有清闲过,他似乎一直走在解决麻烦的路上。最近,他的麻烦又来了。 当地时间6月4日,13位来自OpenAI和Google Deep Mind的现任及前任员工联合发布了…...
Web前端后端精通:深度解析与技能进阶
Web前端后端精通:深度解析与技能进阶 在数字时代的浪潮中,Web前端后端技术的精通成为了信息科技领域的核心竞争力。本文将从四个方面、五个方面、六个方面和七个方面深入探讨Web前端后端技术的精髓,带领读者领略这一领域的魅力与挑战。 一、…...
【C语言】09.函数递归
递归其实是⼀种解决问题的方法,在C语言中,递归就是函数自己调用自己。 一、递归的介绍 1.1递归的思想 把⼀个大型复杂问题层层转化为⼀个与原问题相似,但规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束…...
php高级之框架源码、宏扩展原理与开发
在使用框架的时候我们经常会看到如下代码 类的方法不会显示地声明在代码里面,而是通过扩展的形式后续加进去,这么做的好处是可以降低代码的耦合度、保证源码的完整性、团队开发的时候可以分别写自己的服务去扩展类,减少代码冲突等等。我自己…...
(2024,示例记忆,模型记忆,遗忘,差分评估,概率评估)深度学习中的记忆:综述
Memorization in deep learning: A survey 公和众和号:EDPJ(进 Q 交流群:922230617 或加 VX:CV_EDPJ 进 V 交流群) 目录 0 摘要 1 引言 0 摘要 深度神经网络(DNNs)驱动的深度学习ÿ…...
硬件产品经理
边端协调管理平台 主页一:模型管理1.1 边侧模型管理 二:配置管理2.1 终端软件配置管理 三:设备管理3.1 区域位置管理3.2 工控机管理(其实就是围绕授权)3.3 生产设备管理3.4 设备运行管理 四:数据服务4.1 实…...
AES加密、解密工具类
1、AES加密、解密工具类 这篇文章,主要记录一下AES加密、解密的工具类代码,在需要使用的时候,直接复制黏贴即可。 package com.gitcode.pms.common.util;import org.slf4j.Logger; import org.slf4j.LoggerFactory;import javax.crypto.Cipher; import javax.crypto.spec.…...
普通人想要自学ai,该如何入手,看完这篇你就懂了,零基础教程!
学会了AIGC之后,我只想说:无敌是多么寂寞? 之前我整理一篇会议记录起码要2小时。现在交给AI ,5分钟搞定; 之前整理账目总是出错,现在利用AI财务整合器,轻松解决统计难题; 之前写个…...
Less的简单总结
Less 是一个开源的 CSS 预处理器,它扩展了 CSS 语言,增加了变量、嵌套规则、运算符、函数等特性,使编写 CSS 更加高效、灵活且易于维护。下面是对 "Less" 的一个总结文档: 简介 名称:Less(通常表…...
Android:UI:Drawable:View/ImageView与Drawable
文章目录 在View/ImageVIew中显示DrawableDrawable对View的更新操作在View/ImageVIew中显示Drawable API View.setBackground(Drawable) ImageView.setImagDrawable(Drawable) 源码分析 View.mBackground在View.draw(Canvas)中绘制,调用Drawable.draw(Canvas) ImageView.m…...
网络安全实验BUAA-全套实验报告打包
下面是部分BUAA网络安全实验✅的实验内容 : 认识路由器、交换机。掌握路由器配置的基本指令。掌握正确配置路由器的方法,使网络正常工作。 本博客包括网络安全课程所有的实验报告:内容详细,一次下载打包 实验1-路由器配置实验2-AP…...
监控易监测对象及指标之:全面监控SQL Server 2008
随着企业信息化建设的不断深入,数据库作为存储和管理关键业务数据的核心,其稳定性和性能至关重要。SQL Server 2008作为一款广泛使用的关系型数据库管理系统,承载着众多企业的核心业务数据。 为了确保SQL Server 2008数据库的稳定运行和高效性…...
【学习记录】6.11 阅读记录
SpringBoot多环境配置详解(application-dev.yml、application-test.yml、application-prod.yml) springboot集成mybatis【使用generatorConfig.xml配置自动生成代码】 怎么快速查看自己mysql的安装位置 解决 http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd 报错...
100TOPS算力!16GB内存顶配NVIDIA Jetson Orin NX 16GB 开箱
观前提醒:你以为我斥资6600买了一个NX玩?我其实买了三个NX NVIDIA Jetson Orin NX 简介: NVIDIA Jetson Orin NX是NVIDIA推出的一款高性能边缘计算平台,其设计目标是提供卓越的计算能力以支持各种复杂的人工智能(AI&am…...
OCP学习笔记-007 SQL语言之一:DQL
1. DQL - Data Query Language 命令行提示符修改 SQL> set time on 10:33:58 SQL> define DEFINE _DATE = "11-DEC-22" (CHAR) DEFINE _CONNECT_IDENTIFIER = "orcl" (CHAR) DEFINE _USER = "SYS" (CHAR) DEFINE _P…...
Git之解决重复输入用户名和密码(三十九)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
Python 机器学习 基础 之 【实战案例】轮船人员获救预测实战
Python 机器学习 基础 之 【实战案例】轮船人员获救预测实战 目录 Python 机器学习 基础 之 【实战案例】轮船人员获救预测实战 一、简单介绍 二、轮船人员获救预测实战 三、数据处理 1、导入数据 2、对缺失数据的列进行填充 3、属性转换,把某些列的字符串值转换为数字…...
安全相关的一些基础知识(持续更新)
目录 1. TRNG真随机数生成 2. 对称加密和非对称加密及其区别 3. Hash算法(摘要算法) 4. HTTPS、TLS、SSL、HTTP区别和关系 HTTPS的基本原理 5. PSS 1. TRNG真随机数生成 True Random Number Generator 在真随机数的生成里,把随机数的生…...
使用TensorFlow和Keras对以ResNet50模型进行微调
以下是使用ResNet50进行微调以识别特定的新东西的代码演示。将使用TensorFlow和Keras进行这个任务。 数据集下载地址,解压到工程里面去: https://www.kaggle.com/datasets/marquis03/cats-and-dogs原始代码: from keras.applications…...
Shell脚本要点和难点以及具体应用和优缺点介绍
Shell 脚本是一种用于自动化任务和简化常见系统命令的脚本语言,通常运行在 Unix 或 Unix-like 的系统上,如 Linux 和 macOS。Shell 脚本可以直接在命令行中执行,也可以保存为文件并通过 bash、sh、zsh 等 shell 解释器来执行。 以下是一个简单的 Shell 脚本示例,它演示了如…...
移动端浏览器的扫描二维码实现(vue-qrcode-reader与jsQR方式)
1. 实现功能 类似扫一扫的功能,自动识别到画面中的二维码并进行识别,也可以选择从相册中上传。 2. 涉及到的一些插件介绍 vue-qrcode-reader 一组用于检测和解码二维码的Vue.js组件 jsQR 一个纯粹的javascript二维码阅读库,该库接收原始…...
android中调用onnxruntime框架
创建空白项目 安装Android Studio及创建空白项目参考:【安卓Java原生开发学习记录】一、安卓开发环境的搭建与HelloWorld(详细图文解释)_安卓原生开发-CSDN博客 切记:build configuration language 一定选择Groovy!官…...
【机器学习】与【数据挖掘】技术下【C++】驱动的【嵌入式】智能系统优化
目录 一、嵌入式系统简介 二、C在嵌入式系统中的优势 三、机器学习在嵌入式系统中的挑战 四、C实现机器学习模型的基本步骤 五、实例分析:使用C在嵌入式系统中实现手写数字识别 1. 数据准备 2. 模型训练与压缩 3. 模型部署 六、优化与分析 1. 模型优化 模…...
Apollo9.0 PNC源码学习之Control模块(二)
前面文章:Apollo9.0 PNC源码学习之Control模块(一) 本文将对具体控制器以及原理做一个剖析 1 PID控制器 1.1 PID理论基础 如下图所示,PID各参数(Kp,Ki,Kd)的作用: 任何闭环控制系统的首要任务是要稳、准、快的响…...
直线度测量仪发展历程!
直线度测量仪的发展历程可以概括为以下几个关键阶段: 拉钢丝法: 早期直线度测量的简单直观方法,利用钢丝受重力自然下垂的原理来测量直线度误差。 随着机械设备的大型化和测量精度要求的提高,该方法逐渐无法满足要求,正…...
09-spring的bean创建流程(一)
文章目录 spring中bean的创建流程finishBeanFactoryInitialization(beanFactory)beanFactory.preInstantiateSingletons();getMergedLocalBeanDefinition(beanName);流程实现FactoryBean接口,里面的对象实例化过程 spring中bean的创建流程 finishBeanFactoryInitialization(be…...
spring中基于setting和构造器的注入方式
Spring中可以通过setting和构造器两种方式进行依赖注入。 1.基于setting的注入方式(Setter Injection): 实现方式:在类中添加对应的属性以及对应的setter方法,在配置文件中使用<property>元素进行注入。 示例代码…...
爬虫基本原理?介绍|实现|问题解决
爬虫基本原理: 模拟用户行为: 网络爬虫(Web Crawler)是一种自动化的程序,它模拟人类用户访问网站的方式,通过发送HTTP/HTTPS请求到服务器以获取网页内容。 请求与响应: 爬虫首先构建并发送带有…...
DevOps的原理及应用详解(六)
本系列文章简介: 在当今快速变化的商业环境中,企业对于软件交付的速度、质量和安全性要求日益提高。传统的软件开发和运维模式已经难以满足这些需求,因此,DevOps(Development和Operations的组合)应运而生&a…...
无锡哪里做网站/seo专员是什么
胆大心细做事,低调谦虚做人!转载于:https://blog.51cto.com/09112012/2051790...
龙华app网站制作/优化设计四年级上册数学答案
linux 系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另 外, 由于使用者自己也可以设置计划任务,所以, Linux 系统也提供了使用者控制计划任务的命令 :crontab 命…...
最新流行网站开发技术/最新推广方法
前叙:有灵魂的程序都是每一个程序员的最终目标。TensorFlow了解下? 打算花几个月学机器学习,TensorFlow是很好的选择,折腾了会环境,略有心得分享下。 环境:win10 Python:3.6.5 TensorFlow-GPU&…...
可信的昆明网站建设/东莞市优速网络科技有限公司
pyltp的简介 语言技术平台(LTP)经过哈工大社会计算与信息检索研究中心 11 年的持续研发和推广, 是国内外最具影响力的中文处理基础平台。它提供的功能包括中文分词、词性标注、命名实体识别、依存句法分析、语义角色标注等。 pyltp 是 LTP 的 Python 封装࿰…...
重庆网站页面优化/女生seo专员很难吗为什么
《Kotlin核心编程》阅读笔记第八章 元编程程序和数据什么是元编程常见的元编程技术Kotlin的反射kotlin和Java 反射koltin的KClasskotlin的KCallable获取参数信息Kotlin 注解无处不在的注解精准控制注解位置获取注解信息第八章 元编程 Java的反射只是元编程的一种方式。 示例&a…...
自助众筹网站建设/aso优化教程
环境: win10 64位,联想工作站台式 idea 2018.3社区免费版 问题:alsModel.recommendForAllUsers(2).show报错如下 Exception in thread "main" org.apache.spark.SparkException: Job aborted due to stage failure: Task not se…...