Linux线程(Linux和Windows的线程区别、Linux的线程函数、互斥、同步)

Linux线程(Linux和Windows的线程区别、Linux的线程函数、互斥、同步)

1. 线程介绍

线程的概念

线程是 CPU 调度的基本单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程和进程的区别

  1. 线程是 CPU 调度的基本单位,而进程(内核角度)是承担分配系统资源的基本实体。

  2. 线程和进程最大的区别是,线程之间共用一个地址空间,而进程的地址空间彼此独立。

线程独立的

线程ID、一组寄存器、栈、errno、信号屏蔽字、调度优先级

线程共享的

文件描述符表、每种信号的处理方式( SIG_IGN 、SIG_DFL 或者自定义的信号处理函数)、当前工作目录、用户 id 和组 id

2. Linux和Windows的线程区别

Linux 和 Windows 操作系统之间,采取了完全不同的线程设计思路。Linux 通过进程来模拟线程。由于线程和进程的内容重复度高(都有独立的栈、CPU 调度都要切换上下文等), 所以 Linux 选择了复用进程 PCB ,用 PCB 统一表示执行流。因此,在Linux系统中,实际没有线程和进程的概念,Linux中的 CPU 也不区分进程和线程,统一视为执行流。Linux中的执行流被称为轻量级进程Linux复用 PCB 模拟线程,优点是不需要为线程设计单独的数据结构和调度算法

Windows 创建了独立的 TCB 结构体来创建线程,将线程和进程的概念从底层就完全区分开,这意味着 Windows 的进程和线程是独立的,线程有独立的管理线程 id 、优先级、状态、上下文、连接属性的数据结构,即 Windows 需要分别对进程和线程的代码进行维护,而 ,Windows 对于线程的维护成本要比Linux高。

Thread1

Linux 不区分线程和进程的概念,使得它的线程相关的系统调用,是以 “轻量级进程” 的角度和逻辑来设计和使用的。但对于普通程序员来说,我们并不关心什么 “轻量级进程” ,只是想要使用线程来实现代码的并发。所以,为了解决这个问题,C 语言在 Linux 系统中提供了一个 pthread 库,这个库封装了 Linux 轻量级进程的系统调用,提供了以线程的角度和逻辑来使用轻量级进程的接口。但要注意,使用这个库时,必须要在编译命令上加上一段 -lpthread ,这是因为 pthread 库并不是系统库,但它是系统默认安装的库,它的绝对路径已经被系统环境变量保存下来了,使用时只需要告诉编译器要使用这个第三方库即可。

3. 线程的优缺点

3.1 优点

  1. 创建线程的成本低

    创建进程的成本比创建线程的成本高,因为创建进程意味着要创建PCB、地址空间、页表、构建映射关系……。而创建线程只需要创建PCB。

  2. 线程的调度成本低

    CPU中有一个硬件cache,cache存储着内存的热数据(使用频率高的内存块),CPU在对热数据进行操作时,都是在调度cache里的数据。进程因为有独立的上下文数据,即使不同进程内的cache数据相同,但切换进程寄存器需要恢复彼此的数据,即cache内的数据不互通。但线程共享同一地址空间、上下文数据,它们的cache是互通的,使得线程效率比进程高。

  3. 线程删除成本低

    删除进程要删除PCB、地址空间、页表、构建映射关系……,释放代码和数据。但删除线程只需要删除PCB。

  4. 线程占用的资源比进程少很多。

  5. 能充分利用多处理器的可并行数量。

  6. 在等待慢速I/O操作结束的同时,程序可以执行其他的计算任务。

  7. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。

  8. I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以同时等待多个不同的I/O操作。

3.2 缺点

  1. 性能缺失

    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  2. 健壮性降低

    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  3. 缺乏访问控制

    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  4. 线程不具有进程的独立性,当一个线程崩溃时,其他线程也会挂掉

    进程和线程崩溃时,OS都会向其发送终止信号。进程因为彼此独立,一个挂掉了也不影响另一个;但线程没有进程的独立性,收到终止信号后会一起被杀死。

合理的使用多线程,能提高CPU密集型程序的执行效率,提高IO密集型程序的用户体验

4.线程和地址空间

函数的地址是一批代码的入口地址,函数内每一行代码都有地址,而且同一个函数的地址是连续的。连续的代码地址构成代码块。对函数的拆分,实际就是对地址的拆分,即通过拆分页表实现。把虚拟地址给了谁,谁就拥有了这块虚拟地址,所以虚拟地址的本质是一种资源。

5.Linux的线程函数

Linux 没有线程的概念,只有轻量级进程,所以 Linux 只有轻量级进程的系统调用,用户要使用线程的接口,就需要对轻量级进程接口进行封装,按照线程的接口方式交给用户。所以,在 Linux 中可以说是用户级线程,Windows 是内核级线程。

Linux自带的原生线程库 pthread 库,但该库并非 Linux 系统的默认库,所以在编译时要指明:

gcc -lpthread

5.1 创建线程函数pthread_create

这个函数用于创建一个线程。pthread_t 这个类型本质是 unsigned long int ,库对其进行了封装。

#include <pthread.h>

int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void*(*start_routine)(void *),void* arg)

pthread_t* thread:这是一个输出型参数,它会返回创建的线程id。
const pthread_attr_t* attr:用来设置线程属性。使用时设置为nullptr就可以,一般不需要改。
void*(*start_routine)(void *):函数指针,线程要执行的函数的地址。
void* arg:这个参数会作为线程要执行的函数的参数。

reval:成功返回0,失败返回错误码。

因为 void* argvoid* 类型参数,所以在传参的时候可以传递类类型的地址作为参数,即可以给线程传递多个参数、方法。

上面提到每个线程都有独立的栈,这种传参方式使得其他线程可以间接访问主线程的栈的数据,但由于线程谁先运行是不确定的,这些的操作有巨大的风险,即由于先后次序不确定,其他线程对主线程的栈上数据进行更改操作的时间也是不确定的。

所以在传递类类型作为参数的时候,一般会在堆上开空间(new一个对象),再把这个地址交给新线程,这样这个类类型对象就专属于新线程。

5.2 等待线程函数pthread_join

主线程和 new 出来的新线程运行顺序是不确定的,有可能主线程运行结束了新线程还没开始运行,也有可能新线程运行结束了主线程还没结束,但我们一般期望主线程(main函数)是最后退出的。因为主线程退出,进程就结束了,其他线程还没执行完代码也会强制结束,所以需要在主线程里等待其他线程退出。

pthread_join 就是一个用于等待线程的函数,这个函数会阻塞等待特定的进程退出。且即使其他线程都退出了,main() 还没退出,不使用 join 会造成类似僵尸进程的问题。

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

pthread_t thread:线程id

void **retval:输出型参数,这个参数是获取线程返回值的地址,因为线程函数的返回值是 void* 故这个参数的类型是 void**

线程退出,只考虑正确退出,不考虑异常退出,因为异常了整个进程就崩溃了,所以对于 pthread_join的返回值我们不需要考虑

5.3 终止线程函数若干

进程终止的方法有多种,可以通过函数return返回,pthread_exit() 函数和 pthread_cancel() 函数。但切忌不能使用 exit() 函数退出。

void pthread_exit(void *retval)是一个专门用来终止线程的函数,它用于线程自行调用退出。

int pthread_cancel(pthread_t thread) 用于在主线程取消其他线程,线程被取消退出结果是 -1,本质是定义了一个宏PTHREAD_CANCELED ((void*)-1)

5.4 线程分离函数pthread_detach

一个线程被创建默认是 joinable 的,且必须要 join 。但如果一个线程被分离(detach),线程的工作状态就变成分离状态,不需要被join 也不能被 join(被j oin 会报错)。 线程分离运行主线程脱离等待线程退出的阻塞状态,转而去执行其他操作。

int pthread_detach(pthread_t thread)

pthread_t thread:传入线程的tid即可将线程变更为分离状态。

pthread_t pthread_self(void) 这个函数用于线程获取自己的线程id,线程可以使用该函数自行分离。

6. C++11的多线程

#include<thread>

C++11 支持了多线程,使用 thread 类类型创建线程,但在编译时仍需要 -lpthread ,所以在 Linux 中,c++11的多线程本质就是对原生线程库接口的封装。C++11 这样做的目的,是为了将 Linux、Windows 和 macOS 中 C++ 使用多线程的方式统一,实际上都是对系统的原生线程库接口进行了封装。

所以 pthread 库创建线程,本质也是通过系统调用:

int clone(int (*fn)(void *), void *child_stack,int flags, void *arg, .../* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

这个函数是由 Linux 提供的创建轻量级进程的函数,它可以自定义设置栈的大小,存放的位置等信息,pthread_create() 本质就是对其做了封装。

c++提供了 __pthread 关键字,它允许一个全局变量分别在线程之间私有,修改 __pthread 的变量互不影响,本质是编译器生成了两份数据,因为它们的地址并不相同。这种写法只在 Linux 下有效,且只适用于内置类型。

7. 线程id

pthread库提供给用户提供的线程 id 不是内核的轻量级进程 ,而是 pthread 库维护的一个值。由于 Linux 没有线程的概念,pthread 库就需要自己手搓一个线程属性集合( TCB ),它类似于进程的PCB,只不过PCB是由系统维护的,但 TCB 是 pthread 库维护的。

线程属性集合包含了struct_pthread、线程局部存储、线程栈等信息,所以线程的tid本质是一个指向线程属性集合的起始虚拟地址,在pthread 库中维护。

Thread2

由于线程属性集合都是pthread动态库管理的,它在地址空间的位置位于堆栈之间的共享区。

线程在退出时,会将退出状态写入 struct_pthread 结构体中的某个位置(void ret),将除此之外的其他线程相关的属性都释放掉,等用户通过 pthread_join 函数获取退出状态,并释放资源*。所以说不使用 pthread_join 有可能导致内存泄漏,就是因为不通过 join 获取线程退出状态, struct_pthread 中对应的资源可能会一直保留不释放。

所以,Linux 线程可以认为是由pthread 库中线程的属性集轻量级进程组成的。

8. 线程的互斥

8.1 需要互斥的原因

临界资源是多线程执行流共享的资源,而临界区是每个线程内部,访问临界区的代码。

互斥就是对资源(临界资源)进行保护,即在任何时刻,只允许一个线程进行资源访问。如A线程在对文件写入,线程B对该文件读,有可能发生线程A还没写完数据,线程B就将未写完的数据读出的情况发生,互斥就是对这种情况进行保护。

由于线程有自己的栈,CPU在调度时它的寄存器需要保存线程的数据,在下一次调度时恢复数据。在某些场景下:

int count=1;//count是全局变量
void func()
{while(true){if(count>0){count--;}else{break;}}
}

CPU 中的 eax 寄存器负责进行逻辑运算和算数运算,并在时间片到期时保存运算的结果。

假设将 func() 交给多个线程去执行,CPU 在调度 A 线程时,eax 刚把 if(count > 0) 代码的结果算出来,且为真(1)就切换了线程B。由于此时 count 还是 1 ,CPU 在调度线程 B 时,eax 也把if(count > 0)的结果运算为真,并执行了count--代码,此时全局变量 count 值为 0 。当CPU重新调度线程A时,寄存器恢复了线程 A 的上下文,eax 寄存器中线程 A 的结果仍为真,于是线程 A 也执行了执行了count--代码。最终 count 的值就变为了 -1

8.2 线程锁

pthread库中提供了互斥锁来保护资源,其中pthread_mutex_t是互斥锁类型,使用该类型实例化一个互斥锁即可用于保护资源。pthread库也提供一些用于互斥锁的库函数。需要注意的是,如果互斥锁是全局或静态的,只需要对其 init,不需要 destroy 。

设置删除相关:

int pthread_mutex_destroy(pthread_mutex_t *mutex)

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr)

第一个参数传入锁的地址,第二个参数attr指定了新建互斥锁的属性,若为空则使用默认的互斥锁属性,默认属性为快速互斥锁 。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

加锁解锁相关:

int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)

使用方法:

  1. 使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 语句创建一个名为mutex(或其他)的互斥锁。

  2. 在需要保护的临界区(代码段)使用pthread_mutex_lock() 函数加锁。

  3. 在临界区(代码段)结束行使用pthread_mutex_unlock() 函数解锁。

线程之间设置互斥锁后,被锁的代码段在执行时明显速度会下降,这是因为互斥锁只允许一个线程拥有访问临界区的权限,其他的线程虽然时间片轮询到它们,由于这些线程没有成功申请锁,它们会被 pthread_mutex_lock() 函数阻塞,直到拥有锁的线程解锁成功。所以,在使用锁时,加锁的范围、粒度一定要尽量小

所有线程申请锁,前提是所有的线程都能看到锁,即锁本身也是共享资源,那么,加锁的过程也必须是原子的。所谓的原子性,就是一个事务要么是做了,要么是没做,没有做到一半的中间状态。

8.3 加锁解锁的原理

实现互斥锁是通过系统提供的 swapexchange 指令,将寄存器和内存单元的数据进行交换,该指令由于只有一条,故具有原子性

加锁:

现令每个线程的寄存器中上下文数据初始化为0,线程在加锁的过程中,通过调用swap或exchange指令将内存中的1交换过来,判断只有寄存器内为1的线程上下文为申请锁成功,其他线程由于内存中的值为0,交换后寄存器的值也为0,只能阻塞等待。

解锁:

将寄存器上下文含有1的线程与内存单元数据交换,此时CPU中相关位置的值又回到最初的状态,即实现解锁。

lock:move $0, &alxchgb %al, mutexif(al寄存器的值>0){return 0;}else挂起等待;unlock:move $1, mutex唤起等待Mutex的线程;return 0;

8.4 死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

死锁四个必要条件

互斥条件:一个资源每次只能被一个执行流使用

请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺

循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

  1. 破坏死锁的四个必要条件

  2. 加锁顺序一致

  3. 避免锁未释放的场景

  4. 资源一次性分配

9. 线程的同步

同步,即在保证临界资源安全的前提下,让执行流访问临界资源有一定的顺序。

假设现有两个线程向同一份临界资源里读写数据。A线程向临界资源拿数据,B线程向临界资源放数据,两个线程竞争同一把锁。若其中一方竞争锁的能力大于另一方,如A总是能申请到锁,所以B线程没有机会向临界资源里放数据,但A总是要访问临界资源才能知道里面是否有数据,于是就造成了B线程的饥饿问题。对于这种情景,我们希望线程能按照一定的顺序访问临界资源(申请到锁),于是就需要条件变量来控制。

9.1 条件变量

int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)

线程在调用这个函数时,除了让自己排队等待,还会释放自己传入的锁。返回时,必须先参与锁的竞争,重新上锁才会返回。

int pthread_cond_broadcast(pthread_cond_t *cond)

int pthread_cond_signal(pthread_cond_t *cond)

9.2 信号量

信号量是一种资源的预定机制。预定,即在外部,可以不判断满足资源是否满足条件,就可以知道资源内部的情况。信号量保证它的增减操作皆为原子操作,增操作称为P操作,减操作称为V操作。

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value)

int sem_destroy(sem_t *sem);

int sem_wait(sem_t *sem);

sem_wait()是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。

如果信号量为2,调用sem_wait(),线程将会继续执行,将信号量的值将减到1;如果信号量为0,调用sem_wait(),这个函数就会阻塞等待直到有其它线程将信号量增加到不为0。

如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。

int sem_post(sem_t *sem);

sem_post()是一个原子操作,调用这个函数的信号量会增加"1"。

9. 可重入与线程安全

9.1 可重入与线程安全概念

线程安全:

多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

重入:

同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函函数.。

结论:

可重入的函数是线程安全的,但线程安全的函数不一定可重入。

9.2 常见的可重入与线程安全的情况

常见的线程不安全的情况:

  1. 不保护共享变量的函数。
  2. 函数状态随着被调用,状态发生变化的函数。
  3. 返回指向静态变量指针的函数。
  4. 调用线程不安全函数的函数。

常见的线程安全的情况:

  1. 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。

  2. 类或者接口对于线程来说都是原子操作。

  3. 多个线程之间的切换不会导致该接口的执行结果存在二义性。

常见不可重入的情况:

  1. 调用了 malloc/free 函数,因为 malloc 函数是用全局链表来管理堆的。

  2. 调用了标准 I/0 库函数,标准 I/0 库的很多实现都以木可重入的方式使用全局数据结构。

  3. 可重入函数体内使用了静态的数据结构。

常见可重入的情况:

  1. 不使用全局变量或静态变量。

  2. 不使用 malloc 或者new 开辟出的空间。

  3. 不调用不可重入函数。

  4. 不返回静态或全局数据,所有数据都有函数的调用者提供。

  5. 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

9.3 可重入与线程安全联系与联系

可重入与线程安全联系:

  1. 函数是可重入的,那就是线程安全的。

  2. 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。

  3. 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别:

  1. 可重入函数是线程安全函数的一种。

  2. 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

  3. 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

10. 相关命令

查看运行中的线程的命令

ps -aL			#LWP表示light weight process 轻量级进程,LWP等于PID的线程是主线程

查看CPU属性的指令

lscup

static_cast

static_cast<void*>() 是一个能够安全强制类型转换的类型,对于不能强转的情况会出现报错。由于线程有大量的 void* 类型的参数,所以有大量需要强转的情况,这个类类型可以帮助安全编码。

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

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

相关文章

Large Spatial Model:End-to-end Unposed Images to Semantic 3D 论文解读

目录 一、概述 二、相关工作 1、SfM和可微神经表示 2、端到端的Image-to-3D 三、LSM 1、密集几何预测 2、2D信息特征提取 3、点特征融合 4、可微渲染 5、损失函数 四、实验 一、概述 该论文提出一种大型空间模型&#xff08;Larget Spatial Model,LSM&#xff09;…

Leetcode207. 课程表(HOT100)

链接 题解&#xff1a;先统计入度为0的点&#xff0c;如果一个节点入度为0&#xff0c;说明没有课程指向它&#xff0c;那么你就可以学习它了。反之说明还有先修课。 注意&#xff1a;图存在拓扑排序等价于图不存在环。其实可以想出&#xff1a;如果是一个环&#xff0c;那么…

JavaScript将至

JS是什么&#xff1f; 是一种运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;实现人机交互效果 作用捏&#xff1f; 网页特效 (监听用户的一些行为让网页作出对应的反馈) 表单验证 (针对表单数据的合法性进行判断) 数据交互 (获取后台的数据, 渲染到前…

Centos-stream 9,10 add repo

Centos-stream repo前言 Centos-stream 9,10更换在线阿里云创建一键更换repo 自动化脚本 华为centos-stream 源 , 阿里云centos-stream 源 华为epel 源 , 阿里云epel 源vim /centos9_10_repo.sh #!/bin/bash # -*- coding: utf-8 -*- # Author: make.h

网络安全概论

一、 网络安全是一个综合性的技术。在Internet这样的环境中&#xff0c;其本身的目的就是为了提供一种开放式的交互环境&#xff0c;但是为了保护一些秘密信息&#xff0c;网络安全成为了在开放网络环境中必要的技术之一。网络安全技术是随着网络技术的进步逐步发展的。 网络安…

【Android】android compat理解

1&#xff0c;前提 即便是在同一手机上安装的不同apk&#xff0c;其编译的apk不同&#xff0c;也会导致行为上的差异。如SDK34有限制后台启动&#xff0c;但如果安装的apk所依赖的sdk是33&#xff0c;则不会表现出此差异。这是如何实现的呢&#xff1f;其实&#xff0c;本质是…

《数据结构》学习系列——图(中)

系列文章目录 目录 图的遍历深度优先遍历递归算法堆栈算法 广度优先搜索 拓扑排序定义定理算法思想伪代码 关键路径基本概念关键活动有关量数学公式伪代码时间复杂性 图的遍历 从给定连通图的某一顶点出发&#xff0c;沿着一些边访问遍图中所有的顶点&#xff0c;且使每个顶点…

【C++】static修饰的“静态成员函数“--静态成员在哪定义?静态成员函数的作用?

声明为static的类成员称为类的静态成员&#xff0c;用static修饰的成员变量&#xff0c;称之为静态成员变量&#xff1b;用 static修饰的成员函数&#xff0c;称之为静态成员函数。静态成员变量一定要在类外进行初始化 一、静态成员变量 1)特性 所有静态成员为所有类对象所共…

MySQL面试-1

InnoDB中ACID的实现 先说一下原子性是怎么实现的。 事务要么失败&#xff0c;要么成功&#xff0c;不能做一半。聪明的InnoDB&#xff0c;在干活儿之前&#xff0c;先将要做的事情记录到一个叫undo log的日志文件中&#xff0c;如果失败了或者主动rollback&#xff0c;就可以通…

JavaScript中的this指向绑定规则(超全)

JavaScript中的this指向绑定规则&#xff08;超全&#xff09; 1.1 为什么需要this? 为什么需要this? 在常见的编程语言中&#xff0c;几乎都有this这个关键字&#xff08;Objective-C中使用的是self),但是在JavaScript中的this和常见的面向对象语言中的this不太一样 常见面…

Linux---ps命令

​​​​​​Linux ps 命令 | 菜鸟教程 (runoob.com) process status 用于显示进程的状态 USER: 用户名&#xff0c;运行此进程的用户名。PID: 进程ID&#xff08;Process ID&#xff09;&#xff0c;每个进程的唯一标识号%CPU: 进程当前使用的CPU百分比%MEM: 进程当前使用的…

【Spiffo】环境配置:VScode+Windows开发环境

摘要&#xff1a; 在Linux下直接开发有时候不习惯快捷键和操作逻辑&#xff0c;用Windows的话其插件和工具都更齐全、方便&#xff0c;所以配置一个Windows的开发环境能一定程度提升效率。 思路&#xff1a; 自己本地网络内远程连接自己的虚拟机&#xff08;假定用的是虚拟机…

[ubuntu]编译共享内存读取出现read.c:(.text+0x1a): undefined reference to `shm_open‘问题解决方案

问题log /tmp/ccByifPx.o: In function main: read.c:(.text0x1a): undefined reference to shm_open read.c:(.text0xd9): undefined reference to shm_unlink collect2: error: ld returned 1 exit status 程序代码 #include <stdio.h> #include <stdlib.h> #…

Java基于Spring Boot框架的房屋租赁系统,附源码

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

librdns一个开源DNS解析库

原文地址&#xff1a;librdns一个开源DNS解析库 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 介绍 librdns是一个开源的异步多功能插件式的解析器&#xff0c;用于DNS解析。 源代码地址&#xff1a;GitHub - vstakhov/librdns: Asynchrono…

CTFHUB--yeeclass-web

复现平台CTFHUB靶机为一个完整类论坛网页&#xff0c;题目给了服务端完整代码 代码审计 /src/submit.php Line56-63: 可以看到提交数据存入的时候将$_SESSION["username"]."_"作为前缀&#xff0c;生成了一个uniqid。uniqid的生成方式即{sec:08x}{usec:0…

DataWhale—PumpkinBook(TASK05决策树)

课程开源地址及相关视频链接&#xff1a;&#xff08;当然这里也希望大家支持一下正版西瓜书和南瓜书图书&#xff0c;支持文睿、秦州等等致力于开源生态建设的大佬✿✿ヽ(▽)ノ✿&#xff09; Datawhale-学用 AI,从此开始 【吃瓜教程】《机器学习公式详解》&#xff08;南瓜…

计算机网络socket编程(2)_UDP网络编程实现网络字典

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络socket编程(2)_UDP网络编程实现网络字典 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨…

51c多模态~合集3

我自己的原文哦~ https://blog.51cto.com/whaosoft/12409223 #TextHarmony 字节联合华师提出统一的多模态文字理解与生成大模型 字节跳动与华东师范大学联合提出的TextHarmony模型&#xff0c;这是一个统一的多模态文字理解与生成大模型&#xff0c;它通过创新的Slide-LoRA…

el-table vue3统计计算数字

固定合计在最下列 父组件 <template><el-tablev-loading"loading"tooltip-effect"light":data"list"style"width: 100%":max-height"maxHeight"element-loading-text"拼命加载中...":header-cell-styl…