linux篇【11】:linux下的线程<前序>

news/2024/5/2 14:47:24/文章来源:https://blog.csdn.net/zhang_si_hang/article/details/127901437

目录

一.linux下的线程

1.linux下的线程概念

(1)教材上粗略的 线程 定义

(2)线程的引入

(3)线程真正定义 以及 示意图

 (4)linux 和 windows等其他操作系统的线程对比

(5)LWP

(6)轻量级进程ID与进程ID之间的区别

2.重新定义进程

轻量级进程解释:

(1)线程的优点

(2)线程的缺点

(3)线程异常

(4)线程用途

3.线程和进程的共享/私有资源

4.进程和线程的关系如下图:

二.页表理解——虚拟到物理地址之间的转化

1.页表理解

2.页表的好处

三.线程的接口

1.pthread_create 创建线程

ps -aL (all light)查看所有的轻量级进程

return 退出演示

2.pthread_self

3.thread_join 

4.pthread_exit

(1)pthread_exit 对比 exit

(2)线程退出有3种:

pthread_exit 退出演示

5.pthread_cancel

pthread_ cancel(tid); 退出

四.用户级线程概念

1.线程异常了怎么办?—线程健壮性问题

2.理解 pthread_ t

3.线程栈

 (1)代码区有三类代码

(2)解释 pthread_create 创建线程的返回值pthread_t

(3)线程局部存储

五.分离线程

1.概念

2.示例

(1)pthread_ detach(pthread_ self()); 新线程自我分离。

(2)pthread_ detach(tid1);主线程分离新线程

3.线程分离可以理解为线程退出的第四种方式


一.linux下的线程

1.linux下的线程概念

(1)教材上粗略的 线程 定义

1.在进程内部运行的执行流(线程在进程的虚拟地址空间中运行)
2.线程比进程力度更细调度成本更低
3.线程是CPU调度的基本单位

(2)线程的引入

fork之后,父子是共享代码的
可以通过if else判断,让父子进程执行不同的代码块——>引出不同的执行流,可以做到进行对特定资源的划分

(3)线程真正定义 以及 示意图

 线程(执行流)是系统调度的基本单位!linux下没有真正的线程,Linux的线程是用进程模拟的,他叫做 轻量级进程。线程执行力度比进程更细,调度成本更低(进程切换时不需要切换页表,电地址空间等,只需要切换线程的上下文数据),因为他执行的是进程的一部分,访问的是进程的一部分资源,使用进程的一部分数据

 (4)linux 和 windows等其他操作系统的线程对比

Linux下认为: 

进程和线程在概念上没有区别,他们都叫做执行流
Linux的线程是用进程模拟的(实际上用进程的PCB模拟的)

linux下的tcb就是pcb,因为他们的逻辑结构是一样的

其他操作系统(例如windows)认为:

进程和线程在执行流层面是不一样的,新增了TCB这个结构体,会导致维护成本变高
线程:进程=n:1        进程——PCB;线程——TCB(thread control block)

现在CPU看到的所有的task_ struct都是一个执行流(线程)

(5)LWP

LWP——light wait process:轻量级进程。LWP=PID的执行流是主线程,俗称进程

详细概念:LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化

(6)轻量级进程ID与进程ID之间的区别

因为Linux下的轻量级进程是一个pcb,每个轻量级进程都有一个自己的轻量级进程ID(pcb中的pid),而同一个程序中的轻量级进程组成线程组,拥有一个共同的线程组ID

2.重新定义进程

曾经:进程——内核数据结构+进程对应的代码和数据
现在:进程——内核视角:承担分配系统资源的基本实体 (进程的基座属性),即:向系统申请资源的基本单位!

内部只有一个执行流 task_struct 的进程——单执行流进程
内部有多个执行流 task_struct 的进程——多执行流进程
        线程(执行流)是调度的基本单位!

下面紫色框起来的进程PCB,虚拟内存,页表,内存中的数据和代码,这一组资源的集合叫做一个进程。

轻量级进程解释:

task_ struct <= 传统的进程PCB,如果是单执行流的进程(只有一个task_ struct时),task_ struct = 传统的进程PCB;多执行流的进程task_ struct < 传统的进程PCB

(1)线程的优点

创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

(2)线程的缺点

性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

(3)线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该
进程内的所有线程也就随即退出

(4)线程用途

合理的使用多线程,能提高CPU密集型程序的执行效率
合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是
多线程运行的一种表现)

3.线程和进程的共享/私有资源

进程的多个线程共享 同一地址空间,因此Text SegmentData Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式(SIG_ IGNSIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
进程是资源分配的基本单位
线程是调度的基本单位
线程共享进程数据,但也拥有自己私有的一部分数据:
线程ID
一组寄存器
errno
信号屏蔽字
调度优先级

4.进程和线程的关系如下图:

二.页表理解——虚拟到物理地址之间的转化

1.页表理解

虚拟地址在被转化的过程中,不是直接转化的
虚拟地址是32位的:32bit 分成 10+10+12 
0101   0101  00  0100 0111 11 0000 1110 0101
XXXX XXXX xx   yyyy yyyy  yy zzzz  zzzz zzzz

虚拟地址的前10位在一级页表——页目录中找对应的二级页表;找到对应的二级页表后,中间10位在二级页表中找对应的page的起始地址(物理内存);找到对应的page的起始地址后,后12位作为偏移量在物理内存中的一个page(4KB)中找对应数据的地址,因为后12位有2^12=4096字节=4KB,正好物理内存管理单位是一个page,一个page是4KB,则后12位正好可以覆盖一个page的所有的地址。找到地址后CPU读取物理内存的数据

  

2.页表的好处

(1)进程虚拟地址管理和内存管理,通过页表+page进行解耦
(2)节省空间:分页机制+按需创建页表

页表也要占据内存,页表分离了,可以实现页表的按需创建,比如页目录的第3个地址从来没使用过,就可以不创建对应的二级目录,需要时再创建。一个页表大小是2^32/2^12=2^20字节(页目录和二级页表)

虚拟地址到物理地址的转化——硬件MMU做的,软(页表)硬(MMu)件结合的方式

三.线程的接口

1.pthread_create 创建线程

pthread_create是一个库函数,功能是在用户态创建一个用户线程,而这个线程的运行调度是基于一个轻量级进程LWP实现的。

创建一个新线程的接口,不是系统接口,是linux带的 原生线程库,所以他是第三方库函数,需要在makefile中链接此库,g++ -o $@ $^ -lpthread -std=c++11

 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);    (pthread_t 就是unsigned long int

thread:输出型参数,线程id。attr:线程属性,现在不考虑,设成nullptr。start_routine:线程执行时的回调函数(该线程要执行的函数方法)。arg:线程传递的参数—现在可以传“要创建的线程的名称”,会传给start_routine函数的参数

返回值:成功返回0;失败返回错误码errno 

例如:int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread1"); 新线程会从startRoutine函数进入执行,主线程会拿到n继续执行下面的代码

ps -aL (all light)查看所有的轻量级进程

LWP——light wait process:LWP就是轻量级进程,描述的是一个进程中的一个pcb

makefile: 

mythread:mythread.ccg++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:rm -f mythread

 mythread.cc

return 退出演示

#include<cstdio>
#include<unistd.h>
#include<pthread.h>
#include<iostream>
using namespace std;
void printTid(const char* name,const pthread_t &tid)//引用
{printf("%s 正在运行,thread id:0x%x\n",name,tid);
}void* startRoutine(void* args)
{const char* name=static_cast<const char*>(args);int cnt=5;while(true){printTid(name,pthread_self());sleep(1);if(!(cnt--))break;}cout<<"新线程退出…………"<<endl;return nullptr; 
}
int main()
{pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread 1");sleep(10);pthread_join(tid,nullptr);while(true){printTid("main thread",pthread_self());sleep(1);}return 0;
}

2.pthread_self

 man 3 pthread_self

 pthread_t pthread_self(void);

线程获取自己的线程id

3.thread_join 

 man 3 pthread_join

int pthread_join(pthread_t thread, void **retval);        thread:线程id。retval:输出型参数,线程退出的退出码。(join 不需要退出信号)

线程退出的时候,一般必须要进行join等待,如果不进行join,就会造成类似于进程那样的内存泄露问题。(即:作用是:释放线程资源——前提是线程退出了。并获取线程对应的退出码)

成功返回0;错误返回错误码

4.pthread_exit

终止线程。线程终止--只考虑正常终止

(1)pthread_exit 对比 exit

exit(1):代表退出进程,任何一个 主/新线程调用exit,都表示整个进程退出。pthread_exit()仅仅是代表退出线程。

(2)线程退出有3种:

1. 线程退出的方式,return —— return (void*)111;

2. 线程退出的方式,pthread_exit —— pthread_exit((void*)1111);

3. 线程退出的方式:线程取消请求,pthread_cancel —— pthread_ cancel(tid);

void pthread_exit(void *retval);          retval:线程退出码

pthread_exit 退出演示

#include<cstdio>
#include<unistd.h>
#include<pthread.h>
#include<iostream>
using namespace std;
void printTid(const char* name,const pthread_t &tid)//引用
{printf("%s 正在运行,thread id:0x%x\n",name,tid);
}void* startRoutine(void* args)
{const char* name=static_cast<const char*>(args);int cnt=5;while(true){printTid(name,pthread_self());sleep(1);if(!(cnt--))break;}cout<<"新线程退出…………"<<endl;//return nullptr; pthread_exit((void*)1111);
}
int main()
{pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread 1");sleep(10);pthread_join(tid,nullptr);while(true){printTid("main thread",pthread_self());sleep(1);}return 0;
}

5.pthread_cancel

取消一个线程

int pthread_cancel(pthread_t thread);        thread:线程id

线程退出的方式,给线程发送取消请求, 如果线程是被取消的,退出结果是:-1

pthread_ cancel(tid); 退出

#include<cstdio>
#include<unistd.h>
#include<pthread.h>
#include<iostream>
using namespace std;
void printTid(const char* name,const pthread_t &tid)//引用
{printf("%s 正在运行,thread id:0x%x\n",name,tid);
}void* startRoutine(void* args)
{const char* name=static_cast<const char*>(args);int cnt=5;while(true){printTid(name,pthread_self());sleep(1);if(!(cnt--))break;}cout<<"新线程退出…………"<<endl;//return nullptr; //pthread_exit((void*)1111);
}
int main()
{pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread 1");sleep(10);pthread_cancel(tid);(void)n;cout<<"new thread been canceled"<<endl;void* ret=nullptr;  //void* -> 64 -> 8byte ->空间pthread_join(tid,&ret); //void **retval是 一个输出型参数cout<<"main thread join success,*ret:"<<(long long)ret<<endl;sleep(3);return 0;
}

四.用户级线程概念

1.线程异常了怎么办?—线程健壮性问题

线程异常了——>整个进程整体异常退出。 线程异常==进程异常
线程会影响其他线程的运行一新线程会影响主线程main thread 一健壮性/鲁棒性 较低,

2.理解 pthread_ t

是一个地址
1.线程是一个独立的执行流
2.线程一定会在自己的运行过程中,产生临时数据(调用函数,定义局部变量等)在新线程中修改全局变量后,新线程和主线程都能看到被修改后的结果
3.线程一定需要有自己的独立的栈结构

3.线程栈

 (1)代码区有三类代码

我们使用的线程库,用户级线程库,库的名字叫pthread

代码区有三类代码:

①你自己写的代码。

②库的接口代码。(例如动态库libpthread. so会写入内存,通过页表映射到进程的共享区,代码区的库接口代码通过跳转到共享区执行完库中的代码,然后再跳转回代码区继续执行)

③系统接口代码。(通过身份切换 用户—>内核 执行代码)

所有的代码执行,都是在进程的地址空间当中进行执行的


(2)解释 pthread_create 创建线程的返回值pthread_t

用户要用线程,但是OS没有线程的概念,libpthread. so线程库起承上启下的作用。

共享区内: 

线程的全部实现,并没有全部体现在OS内,而是OS提供执行流,具体的线程结构由库来进行管理。库可以创建多个线程->库也要管理线程->管理:先描述,在组织

struct thread_ info

        pthread_ t tidh .
        void *stack; //私有栈
        ……
}  

libpthread. so线程库映射进共享区中。创建线程时,线程库中也会创建一个 结构体struct thread_ info叫做 线程控制块线程控制块内部是描述线程的信息,内部有一个指针指向mm_struct用户空间的一块空间——线程栈。创建线程成功后,返回一个pthread_t类型的地址,pthread_t类型的地址保存着我们共享区中对应的用户级线程的线程控制块的起始地址!

 结论:主线程的独立栈结构,用的就是地址空间中的栈区;新线程用的栈结构,用的是库中提供的栈结构(这个线程栈是库维护的,空间还是用户共享区提供的)

Linux中,线程库用户级线程库,和内核的LWP是1:1(LWP(类比PID)——light wait process:轻量级进程编号。LWP=PID的执行流是主线程,俗称进程

(3)线程局部存储

线程库中的结构体struct thread_ info,内部是描述线程的信息,struct thread_ info中还有一个叫做线程局部存储的区域。作用:可以把全局变量私有化

正常情况全局变量是多个线程可以同时修改的:

加上__thread,把全局变量拷贝给每个进程各一份,使全局变量私有化,各自修改自己的全局变量

五.分离线程

1.概念

①默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
线程分离:如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
        int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
        pthread_detach(pthread_self());
一个线程如果分离了就不能再join,joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
 int pthread_detach(pthread_t thread);

2.示例

(1)pthread_ detach(pthread_ self()); 新线程自我分离。

如果没有sleep(1),就会出现新线程一直循环的情况。因为有可能主线程先执行 int n = pthread_ join(tid1, nullptr); 此时新线程还没有pthread_ detach分离,则主线程会一直阻塞在pthread_ join这里,不会返回。

当有sleep(1)时,新线程会先pthread_ detach自我分离。主线程后执行 int n = pthread_ join(tid1, nullptr); 此时新线程已经pthread_ detach分离,则主线程会直接pthread_ join返回错误码,证明了一个线程如果分离了就不能再join

(2)pthread_ detach(tid1);主线程分离新线程

建议主线程分离新线程,因为这样主线程一定是先分离了新线程,再pthread_ join。

3.线程分离可以理解为线程退出的第四种方式

(1)线程分离分为立即分离,延后分离,要保证线程还活着。线程分离意味着,我们不在关心这个线程的死活。线程分离可以理解为线程退出的第四种方式——延后退出

(2)主线程的退出,并不会导致进程退出,也不会影响其他线程的运行。进程中的所有线程退出了,进程才会退出。—— 一般我们分离线程,对应的main thread不要退出(常驻内存的进程)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_225338.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新的趋势:From Big to Small and Wide data

新的趋势&#xff1a;From Big to Small and Wide data 所以&#xff0c;在这个时候&#xff0c;作为率先提出要做 MySQL 开源 HTAP 数据库的 StoneDB&#xff0c;想要稍微冷静一下。 不是说我们不做 HTAP 了&#xff0c;而是有了一个新的思路。这个思路&#xff0c;也同样来…

【亲测】网址引导页管理系统

介绍&#xff1a; 易航网址引导系统-网址引导页管理系统去授权版一款极其优雅的易航网址引导页管理系统&#xff0c; 如果有问题可以跟我反馈&#xff0c;共同进步。祝各位道友一路飞升&#xff0c;顶峰相见&#xff01;内置12套模板和防墙插件。 项目亮点&#xff1a; 1、…

Redis基础命令(String类型)Value为JSON

目录 String类型&#xff08;存储的值为JSON形式&#xff09; 问题&#xff1a; 解决办法&#xff1a; 示例&#xff1a; 实际操作&#xff1a; 总结&#xff1a; String类型&#xff08;存储的值为JSON形式&#xff09; 问题&#xff1a; Redis没有类似MySql中的表的概…

2022年戈登·贝尔奖授予等离子体加速器突破研究

ACM 总裁Cherri Pancake&#xff08;图片来源&#xff1a;网络&#xff09; 11月17日&#xff0c;在达拉斯举行的SC22颁奖典礼上&#xff0c;ACM将2022年戈登贝尔奖&#xff08;Gordon Bell Prize&#xff09;授予了一组研究人员&#xff0c;他们利用四台超级计算机&#xff08…

【Flink】基本转换算子使用之fliter、flatMap,键控流转换算子和分布式转换算子

文章目录一 Flink DataStream API1 基本转换算子的使用&#xff08;1&#xff09;flitera 使用匿名类实现b 使用外部类函数实现b 使用flatMap实现&#xff08;2&#xff09;flatMapa 使用匿名类实现b 使用匿名函数实现2 键控流转换算子&#xff08;1&#xff09; keyBy&#xf…

中国互联网众筹行业

近些年&#xff0c;中国互联网发展迅速&#xff0c;众筹这种起源于美国的新型互联网金融模式更是一直处于风口浪尖。在“大众创业、万众创新”的背景下&#xff0c;这种低门槛的融资模式也深受欢迎&#xff0c;加上阿里、京东、苏宁三大电商的巨头的相继入场&#xff0c;更令这…

IMS各网元的主要功能

文章目录用户注册时&#xff1a; 手机发出一个注册消息到他所在的拜访地的P。 比如&#xff0c;他是山西太原的用户&#xff0c;他这时候到了北京&#xff0c;那么这个时候&#xff0c;他要注册到IMS网络里面的话&#xff0c;这个P-CSCF就是北京的P-CSCF&#xff0c;这个北京的…

CAS号:376364-38-4,rCRAMP (rat)

rCRAMP (rat) 是一种大鼠组织蛋白酶相关的抗菌肽&#xff0c;有助于大鼠脑肽/蛋白质提取物的抗菌活性。rCRAMP (rat) 是大鼠中枢神经系统先天免疫系统的关键参与者。rCRAMP (rat) is the rat cathelin-related antimicrobial peptide. rCRAMP (rat) contributes to the antibac…

Kotlin 开发Android app(十一):Android控件RecyclerView

Android 中的控件非常的丰富&#xff0c;我们会陆陆续续的进行介绍&#xff0c;从第九节开始&#xff0c;关于Kotlin 的语法特性就差不多结束&#xff0c;后面如果有发现需要说明的语法&#xff0c;再进行相关的补充。 在Android的控件中&#xff0c;RecyclerView算是一个大控…

从 Uber 数据泄露事件我们可以学到什么?

Uber 数据泄露始于一名黑客从暗网市场购买属于一名 Uber 员工的被盗凭证。最初尝试使用这些凭据连接到 Uber 的网络失败&#xff0c;因为该帐户受 MFA 保护。为了克服这一安全障碍&#xff0c;黑客通过 What’s App 联系了 Uber 员工&#xff0c;并假装是 Uber 的安全人员&…

OA系统,有效提升企业办公效率落实执行力

企业管理的成功将最终取决于企业的执行情况&#xff0c;只要有良好的经营管理&#xff0c;管理系统&#xff0c;一个好的领导者&#xff0c;充分调动员工的积极性&#xff0c;将能最大限度的管理执行力。 OA协同办公系统提供了工作流和协同工作互补结合。工作流程严格规定了工作…

大数据面试题(四):Yarn核心高频面试题

文章目录 Yarn核心高频面试题 一、简述Hadoop1与Hadoop2的架构异同 二、为什么会产生yarn&#xff0c;它解决了什么问题&#xff0c;有什么优势&#xff1f; 三、HDFS的数据压缩算法&#xff1f;及每种算法的应用场景&#xff1f; 1、gzip压缩 2、Bzip2压缩 3、Lzo压缩 …

为什么 NGINX 的 reload 不是热加载?

作者&#xff1a;刘维 这段时间在 Reddit 看到一个讨论&#xff0c;为什么 NGINX 不支持热加载&#xff1f;乍看之下很反常识&#xff0c;作为世界第一大 Web 服务器&#xff0c;不支持热加载&#xff1f;难道大家都在使用的 nginx -s reload 命令都用错了&#xff1f; 带着这个…

数据治理系列:数仓建模之数仓主题与主题域

背景&#xff1a; 数据仓库之父 Bill Inmon 将数据仓库描述为一个面向主题的、集成的、稳定的、反应历史变化的数据集合&#xff0c;用于支持管理者的决策过程。 从上面的引言里面&#xff0c;我们其实可以知道主题在数仓建设里面绝对是很重要的一环&#xff0c;这的确是的。…

【计算机网络】HTTP/HTTPS协议基础知识汇总

目录 1.URL&#xff1a; 2.HTTP协议&#xff1a; 2.1抓包工具&#xff08;这里用fiddler&#xff09;&#xff1a; 2.2请求和响应的格式&#xff1a; 2.3方法的介绍&#xff1a; 2.4请求报头&#xff08;header&#xff09;&#xff1a; 2.5状态码&#xff1a; 2.6响应…

antd——使用a-tree组件实现 检索+自动展开+自定义增删改查功能——技能提升

之前写后台管理系统时&#xff0c;遇到一个下面的需求&#xff0c;下面是最终完成的效果图。 实现的功能有&#xff1a; 1. 下拉 选择不同的类型——就是一个普通的select组件&#xff0c;下面并不做介绍 2. 通过关键字可以进行tree树形结构的筛选&#xff0c;然后将筛选后的…

Python_数据容器_元组tuple

一、元组tuple定义 为什么需要元组 列表是可以修改的&#xff0c;如果想要传递的信息不被篡改&#xff0c;列表就不适合了 元组和列表一样&#xff0c;都是可以封装多个不同类型的元素在内 最大的不同点在于&#xff1a; 元祖一旦定义完成&#xff0c;就不可修改 所以&am…

LabVIEW使用Desktop Execution Trace工具包

LabVIEW使用Desktop Execution Trace工具包 可以使用桌面执行跟踪工具包来调试和优化大型LabVIEW应用程序&#xff0c;包括具有多个循环的应用程序、客户端-服务器架构、动态加载VI等。该工具包从本地或远程计算机桌面上运行的应用程序捕获执行事件&#xff0c;并在表窗格中显…

聊一聊如何截获 C# 程序产生的日志

一&#xff1a;背景 1.讲故事 前段时间分析了一个dump&#xff0c;一顿操作之后&#xff0c;我希望用外力来阻止程序内部对某一个com组件的调用&#xff0c;对&#xff0c;就是想借助外力实现&#xff0c;如果用 windbg 的话&#xff0c;可以说非常轻松&#xff0c;但现实情况…

48种数据分析可视化图表

可视化对于数据分析师来说可能不是最重要的&#xff0c;重要的是你分析或挖掘出来的结果是否有效。在这基础之上就需要通过可视化恰当完整的表达见解。这里又有区别了&#xff1a;实用性和美观性哪个更重要&#xff1f;要我说实用性是第一位的&#xff0c;能用一个元素表达最好…