Linux 线程控制
2. Linux 线程控制
首先,**内核中有没有很明确的线程的概念**,而有**轻量级进程的概念**。当我们想写多线程代码时,可以使用**POSIX线程库**,这是一个 处于应用层位置的库,几乎所有的Linux发行版都默认带这个库,使用时需要引入头文件`pthread.h`
2.1 创建线程
2.1.1 基础创建
功能:创建一个新的线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数:
thread:输出型参数,返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
- 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误
- pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
- pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小
由于是第三方库,所以编译时要加上-lpthread
myThread : myThread.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY : clean
clean:rm -f myThread
可以看到,这不同的线程拥有同一个进程的pid
当我们想观察线程状态时,可以使用ps -aL
命令,LWP的时ligtht weight process的缩写,PID等于LWP的线程是主线程。当我们使用kill -9命令时,杀掉任意一个线程,整个进程都会被kill掉
下面这个可以持续观察线程状态
while :; do ps -aL | head -1 && ps -aL | grep 'myThread' | grep -v ps; echo "##############################################"; sleep 1; done
2.1.2 让一个函数被重入,让多个执行流同时调用
// 可以被多个执行流执行
void Show(const string& s)
{cout << s << " say: hello!" << endl;
}void* ThreadRoutine(void* args)
{while(true) {// cout << "new thread, pid is " << getpid() << endl;Show("[new thread]");sleep(1); }
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);while(true) {// cout << "main thread, pid is " << getpid() << endl;Show("[main thread]");sleep(1); }return 0;
}
虽然打印出现了错行的情况,但不影响现象的产生
2.1.3 线程之间进行通信很容易
int g_val = 0;void* ThreadRoutine(void* args)
{while(true) {// cout << "new thread, pid is " << getpid() << endl;// Show("[new thread]");printf("I am new thread, g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1); }
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);while(true) {// cout << "main thread, pid is " << getpid() << endl;// Show("[main thread]");g_val++;printf("I am main thread, g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1); }return 0;
}
2.1.4 线程任意一个出现异常,进程都会退出
void* ThreadRoutine(void* args)
{while(true) {cout << "new thread, pid is " << getpid() << endl;sleep(5);int *p =nullptr;*p = 1;}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);while(true) {cout << "main thread, pid is " << getpid() << endl;sleep(1); }return 0;
}
2.1.5 观察类型是pthread_t 的 tid
定义 pthread_t 类型是 无符号长整型
typedef unsigned long int pthread_t;
可以打印查看
int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);while(true) {printf("I am main thread, creat new thread tid is %lu\n");sleep(1); }return 0;
}
可以看到,打印出来的tid的值与LWP的值完全不一样,这是因为tid是给用户使用的的,而LWP是给OS使用的。
实际上,tid充当的是地址,在2.4中会介绍
2.1.6 给子线程传递参数
void* ThreadRoutine(void* args)
{const char* name = (const char*)args;while(true) {printf("%s is running\n", name);sleep(1); }
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void*)"new thread");while(true) {printf("main thread is running\n");sleep(1); }return 0;
}
2.2 线程等待
2.2.1 为什么需要线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
- 如果需要,可以获取子线程的返回值
2.2.2 基础使用
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED,其值为-1。
- 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
void* ThreadRoutine(void* args)
{const char* name = (const char*)args;int cnt = 5;while(cnt--) {printf("%s is running\n", name);sleep(1); }return nullptr; // 线程走到这里后,会默认退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void*)"new thread");pthread_join(tid, nullptr); // 主线程等待的时候,默认是阻塞等待cout << "main thread quit" << endl;return 0;
}
2.2.3 获取子线程的返回值
void* ThreadRoutine(void* args)
{const char* name = (const char*)args;int cnt = 5;while(cnt--) {printf("%s is running\n", name);sleep(1); }return (void*)1; // 线程走到这里后,会默认退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void*)"new thread");void* ret;pthread_join(tid, &ret); // 主线程等待的时候,默认是阻塞等待printf("main thread quit, ret: %d\n", (long long)ret); // 由于void* 是8字节,所以这里要用8字节的long long 来进行强转return 0;
}
如果想要多返回些信息,可以使用类
struct Request
{Request(int s, int e, string n) : _start(s), _end(e), _name(n) {}// 累加 [start, end]int Run(){int tmp = 0;for(int i = _start; i<=_end; ++i) {tmp += i;}return tmp;}int _start;int _end;string _name;
};struct Response
{Response(int r, int e) : _res(r), _exitCode(e) {}int _res;int _exitCode;
};void* GetSum(void* args)
{Request* rq = static_cast<Request*>(args);Response* rs = new Response(0, 0);// 计算rs->_res = rq->Run();delete rq;return rs;
}int main()
{pthread_t tid;Request* rq = new Request(1, 100, "new Thread");pthread_create(&tid, nullptr, GetSum, rq);void* ret;pthread_join(tid, &ret);Response* rs = static_cast<Response*>(ret);printf("res: %d, exitCode: %d\n", rs->_res, rs->_exitCode);delete rs;return 0;
}
以这个为例子,如果一个计算任务很大,比如1-100000,就可以拆分,让不同的线程执行不同的范围,最后主线程再将子线程的结果进行汇总
2.3 线程终止
2.3.1 直接使用return
上面的所有例子都是使用这个方法,当线程走到return后,会默认退出。注意:exit()是进程退出函数,不是线程,当线程函数使用它时会使整个进程退出
2.3.2 pthread_exit
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
void* ThreadRoutine(void* args)
{const char* name = (const char*)args;int cnt = 5;while(cnt--) {printf("%s is running\n", name);sleep(1); }pthread_exit((void*) 123);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void*)"new thread");void* ret;pthread_join(tid, &ret); // 主线程等待的时候,默认是阻塞等待printf("main thread quit, ret: %d\n", (long long)ret); // 由于void* 是8字节,所以这里要用8字节的long long 来进行强转return 0;
}
2.3.3 pthread_cancel
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
void* ThreadRoutine(void* args)
{const char* name = (const char*)args;while(true) {printf("%s is running\n", name);sleep(1); } return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void*)"new thread");// 主进程取消掉子进程sleep(1);pthread_cancel(tid);void* ret;pthread_join(tid, &ret); // 主线程等待的时候,默认是阻塞等待printf("main thread quit, ret: %d\n", (long long)ret); // 由于void* 是8字节,所以这里要用8字节的long long 来进行强转return 0;
}
可以看到ret为-1,那是因为PTHREAD_ CANCELED,其值为-1。见2.2.2
2.4 线程ID及进程地址空间布局
- pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事,(pid和LWP)
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程
- pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
功能:pthread_self - 获取调用线程的ID原型:pthread_t pthread_self(void);
描述:pthread_self()函数返回调用线程的ID。与在创建this的pthread_create(3)调用中的*thread中返回的值线程是一样的。
返回值:这个函数总是成功,返回调用线程的ID。
// 将tid以地址的格式打出来,即16进制
string ToHex(pthread_t tid)
{char hex[64];snprintf(hex, sizeof(hex), "%p", tid);return hex;
}void* ThreadRoutine(void* args)
{while(true) {// 打印出进程id,将它转成16进程// cout << "thread id: " << ToHex(pthread_self()) << endl;printf("thread id: %s\n", ToHex(pthread_self()).c_str());sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, nullptr);while(true) {printf("child tid: %s\n", ToHex(tid).c_str());sleep(1);}pthread_join(tid, nullptr);return 0;
}
pthread_t
到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
可以看到,除了主线程,所有其他线程的独立栈,都在共享区,具体来讲是在pthread库中,tid指向的用户tcb中,这样,线程在调度运行的时候就不会互相干扰了。
2.5 其它问题
2.5.1 创建多个线程
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>
#define NUM 3
using namespace std;struct ThreadData
{ ThreadData(string name) : _threadName(name) {}string _threadName;
};void* ThreadRoutine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5;while(cnt--) {printf("%s, tid: %p, pid: %d\n", td->_threadName.c_str(), pthread_self(), getpid());sleep(1);}delete td;return nullptr;
}int main()
{// 创建多个线程vector<pthread_t> tids;for (size_t i = 0; i < NUM; i++) {pthread_t tid;ThreadData* td = new ThreadData("Thread-" + to_string(i+1));// 给子线程传递数据,堆空间是共享的pthread_create(&tid, nullptr, ThreadRoutine, td);tids.push_back(tid);}// 等待线程for(const auto& t : tids) {pthread_join(t, nullptr);}return 0;
}
2.5.2 每一个线程自己独立的栈结构
修改ThreadRoutine()
函数
void* ThreadRoutine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5, x = 0;while(cnt--) {printf("%s, tid: %p, pid: %d, x: %d, &x: %p\n", td->_threadName.c_str(), pthread_self(), getpid(), x, &x);x++;sleep(1);}delete td;return nullptr;
}
可以看到,每一个线程都独享一个x,即独立的栈空间。注意这里是独立,并不是私有,其它线程想访问还是可以的,比如主线程想访问Thread-2的x值
int *p = nullptr;void* ThreadRoutine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5, x = 0;while(cnt--) {printf("%s, tid: %p, pid: %d, x: %d, &x: %p\n", td->_threadName.c_str(), pthread_self(), getpid(), x, &x);x++;// 获取该线程的xif(td->_threadName == "Thread-2") p = &x;sleep(1);}delete td;return nullptr;
}int main()
{// 创建多个线程vector<pthread_t> tids;for (size_t i = 0; i < NUM; i++) {pthread_t tid;ThreadData* td = new ThreadData("Thread-" + to_string(i+1));// 给子线程传递数据,堆空间是共享的pthread_create(&tid, nullptr, ThreadRoutine, td);tids.push_back(tid);}sleep(2);printf("Main Thread get x: %d, &x: %p\n", *p, p);// ...
}
2.5.3 全局变量
默认情况下,全局变量被所有线程共享
// 共享资源
int gVal = 100;void* ThreadRoutine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5;while(cnt--) {gVal++;printf("%s, tid: %p, pid: %d, gVal: %d, &gVal: %p\n", td->_threadName.c_str(), pthread_self(), getpid(), gVal, &gVal);sleep(1);}delete td;return nullptr;
}
如果想要让每个进程独享该变量,可以在变量前加上__thread,让编译器把该变量放到tcb的线程局部存储单元中,见2.4。注意这里只能初始化内置类型,自定义类型是不可以的
__thread int gVal = 100;
介绍一个__thread的用法:减少系统调用
__thread unsigned long int self = 0;
__thread int tPid = 0;
void* ThreadRoutine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);self = pthread_self();tPid = getpid();int cnt = 5;while(cnt--) {printf("%s, tid: %p, pid: %d\n", td->_threadName.c_str(), self, tPid);sleep(1);}delete td;return nullptr;
}
像上面这样,就减少了系统调用的次数。可以将这样的变量理解为线程级别的全局变量,不同线程之间变量互不干扰。
2.6 分离线程
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable又是分离的
__thread unsigned long int self = 0;
__thread int tPid = 0;
void* ThreadRoutine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);self = pthread_self();tPid = getpid();// 自己分离pthread_detach(self);int cnt = 5;while(cnt--) {printf("%s, tid: %p, pid: %d\n", td->_threadName.c_str(), self, tPid);sleep(1);}delete td;return nullptr;
}int main()
{// 创建多个线程vector<pthread_t> tids;for (size_t i = 0; i < NUM; i++) {pthread_t tid;ThreadData* td = new ThreadData("Thread-" + to_string(i+1));// 给子线程传递数据,堆空间是共享的pthread_create(&tid, nullptr, ThreadRoutine, td);tids.push_back(tid);}sleep(2);// 等待线程for(const auto& t : tids) {int ret = pthread_join(t, nullptr);printf("ret: %d, who: %p, why: %s\n", ret, t, strerror(ret));}return 0;
}
相关文章:

Linux 线程控制
2. Linux 线程控制 首先,**内核中有没有很明确的线程的概念**,而有**轻量级进程的概念**。当我们想写多线程代码时,可以使用**POSIX线程库**,这是一个 处于应用层位置的库,几乎所有的Linux发行版都默认带这个库&#x…...

内网通3.4.3045广告码、积分码
内网通3.4.3045广告码、积分码 https://download.csdn.net/download/weixin_42120669/89772091...

MATLAB给一段数据加宽频噪声的方法(随机噪声+带通滤波器)
文章目录 引言方法概述完整代码:结果分析结论参考文献引言 在信号处理领域,添加噪声是模拟实际环境中信号传输时常见的操作。宽频噪声可以用于测试系统的鲁棒性和信号处理算法的有效性。本文将介绍如何使用 M A T L A B MATLAB MATLAB给一段数据添加宽频噪声,具体方法是结合…...

网安标委发布敏感个人信息识别指南
9月14日全国网络安全标准化技术委员会秘书处发布《网络安全标准实践指南——敏感个人信息识别指南》 敏感个人信息识别规则: 一旦遭到泄露或者非法使用,容易导致自然人的人格尊严受到侵害、自然人的人身安全受到危害、自然人财产安全受到危害。 注意&am…...

音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现
音视频入门基础:AAC专题系列文章: 音视频入门基础:AAC专题(1)——AAC官方文档下载 音视频入门基础:AAC专题(2)——使用FFmpeg命令生成AAC裸流文件 音视频入门基础:AAC…...

几何 | 数学专项
日期内容2024.9.19创建 { d > 0 , 递增数列 d < 0 , 递减数列 d 0 ,常数列 \begin{cases} d>0,递增数列\\ d<0,递减数列\\ d0,常数列 \end{cases} ⎩ ⎨ ⎧d>0,递增数列d<0,递减数列d0,常数列 【2010.13】 【1.历年真…...

学习CubeIDE——定时器开发
在b站上学习洋桃电子关于HAL库开发,发现使用CubeIDE是真的简单又方便。 实验现象:使用定时器来产生中断,中断程序是LED灯翻转 在我看来,定时器,是一个从0开始增1(常规),增加到一定…...

【Elasticsearch】-图片向量化存储
需要结合深度学习模型 1、pom依赖 注意结尾的webp-imageio 包,用于解决ImageIO.read读取部分图片返回为null的问题 <dependency><groupId>org.openpnp</groupId><artifactId>opencv</artifactId><version>4.7.0-0</versio…...
二叉树(一)高度与深度
高度:从最底层往上数(后序遍历,左右根),更简单(递归) 深度:从上往下数直到有叶子(前序遍历,根左右),较复杂 高度是最大深度 一、求…...
梧桐数据库(WuTongDB):MySQL 优化器简介
MySQL 优化器是数据库管理系统中的一个重要组件,用于生成并选择最优的查询执行计划,以提高 SQL 查询的执行效率。它采用了基于代价的优化方法(Cost-Based Optimizer, CBO),通过评估不同查询执行方案的代价,…...

交通运输部力推高速公路监测,做好结构安全预警,保护人民安全
在快速发展的交通网络中,高速公路作为经济命脉与生命通道,其结构安全直接关系到每一位行路者的生命财产安全。为此,广东省交通运输厅正式发布《关于积极申报高速公路监测预警应用示范揭榜的通知》,旨在通过技术创新与应用示范&…...

基于PHP+MySQL组合开发的在线客服源码系统 聊天记录实时保存 带完整的安装代码包以及搭建部署教程
系统概述 随着互联网技术的飞速发展,企业与客户之间的沟通方式日益多样化,在线客服系统作为连接企业与客户的桥梁,其重要性不言而喻。然而,市场上现有的在线客服系统往往存在成本高、定制性差、维护复杂等问题。针对这些痛点&…...
NEXT.js 创建postgres数据库-关联github项目-连接数据库-在项目初始化数据库的数据
github创建项目仓库创建Vercel账号选择hobby连接github仓库install - deploy创建postgres数据库(等待deploy完成) Continue to DashboardStorage(头部nav哪里)create Postgresconnect连接完后,切换到.env.local&#x…...

Matlab如何配置小波工具(Wavelet Toolbox)
1、发现问题 因为实验要使用小波工具函数,运行时报错如下: 查看对应文件夹发现没有小波工具(也可在控制台输入ver),检查是否有该工具,输入后回车返回如下: 2、下载工具包 没有这个工具就要去下…...

FTP、SFTP安装,整合Springboot教程
文章目录 前言一、FTP、SFTP是什么?1.FTP2.SFTP 二、安装FTP1.安装vsftp服务2.启动服务并设置开机自启动3.开放防火墙和SELinux4.创建用户和FTP目录4.修改vsftpd.conf文件5.启动FTP服务6.问题 二、安装SFTP1、 创建用户2、配置ssh和权限3、建立目录并赋予权限4、启动…...

24年蓝桥杯及攻防世界赛题-MISC-3
21 reverseMe 复制图片,在线ocr识别,https://ocr.wdku.net/,都不费眼睛。 22 misc_pic_again ┌──(holyeyes㉿kali2023)-[~/Misc/tool-misc/zsteg] └─$ zsteg misc_pic_again.png imagedata … text: “$$KaTeX parse error: Undefined…...

阿里云容器服务Kubernetes部署新服务
这里部署的是前端项目 1.登录控制台-选择集群 2.选择无状态-命名空间-使用镜像创建 3.填写相关信息 应用基本信息: 容器配置: 高级配置: 创建成功后就可以通过30006端口访问项目了...

记录生产环境,通过域名访问的图片展示不全,通过ip+端口的方式访问图片是完整的
原因:部署nginx的服务器硬盘满了 排查发现nginx日志文件占用了大量硬盘 解决方案: 删除该文件,重启nginx服务,问题解决。...

网络安全实训八(y0usef靶机渗透实例)
1 信息收集 1.1 扫描靶机IP 1.2 收集靶机的端口开放情况 1.3 探测靶机网站的目录 1.4 发现可疑网站 1.5 打开可疑网站 2 渗透 2.1 使用BP获取请求 2.2 使用工具403bypasser.py探测可疑网页 2.3 显示可以添加头信息X-Forwarded-For:localhost来访问 2.4 添加之后转发ÿ…...
QT信号槽原理是什么,如何去使用它?
QT的信号槽(Signals and Slots)机制是QT框架的核心特性之一,它提供了一种对象间通信的方式,使得QT的部件可以在不知道彼此详细实现的情况下相互通信。这种机制在图形用户界面编程中尤为重要,因为它有助于降低对象间的耦…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁
赛门铁克威胁猎手团队最新报告披露,数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据,严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能,但SEMR…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
2024年初,人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目(一款融合大型语言模型能力的云端AI编程IDE)时,技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力,TRAE在WayToAGI等…...
统计学(第8版)——统计抽样学习笔记(考试用)
一、统计抽样的核心内容与问题 研究内容 从总体中科学抽取样本的方法利用样本数据推断总体特征(均值、比率、总量)控制抽样误差与非抽样误差 解决的核心问题 在成本约束下,用少量样本准确推断总体特征量化估计结果的可靠性(置…...