linux篇【11】:linux下的线程

news2025/1/11 11:12:14

目录

一.linux下的线程

1.linux下的线程概念

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

(2)线程的引入

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

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

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.线程分离可以理解为线程退出的第四种方式

六.线程互斥

1.三个概念

2.互斥

(3)抢票中的问题

3.加锁

(2)定义/释放 互斥锁

(3)加锁、解锁

(4)使用锁代码


一.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都是一个执行流(线程)

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 Segment Data Segment 都是共享的 , 如果定义一个函数 , 在各线程中都可以调用, 如果定义一个全局变量 , 在各线程中都可以访问到 , 除此之外 ,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式 (SIG_ IGN SIG_ 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 创建线程

创建一个新线程的接口,不是系统接口,是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(类比PID)——light wait process:轻量级进程编号。LWP=PID的执行流是主线程,俗称进程

makefile: 

mythread:mythread.cc
	g++ -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 ——

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不要退出(常驻内存的进程)

六.线程互斥

1.三个概念

1.临界资源:多个执行流都能看到并能访问的资源,临界资源
2.临界区:多个执行流代码中有不同的代码,访问临界资源的代码,我们称之为临界区
3.互斥:当我们访问某种资源的时候,任何时刻。都只有一个执行流在进行访问,这个就叫做:互斥特性

2.互斥

没有互斥时,以抢票,票数减1: int tickets;   tickets--;为例

(1)在执行语句的任何地方,线程可能被切换走

int tickets;
tickets--;tickets--是由3条语句完成的:

tickets--:有三步
① load tickets to reg
② reg-- ;
③ write reg to tickets

(2)CPU内的寄存器是被所有的执行流共享的,但是寄存器里面的数据是属于当前执行流的上下文数据。线程被切换的时候,需要保存上下文;线程被换回的时候,需要恢复上下文。 

(3)抢票中的问题

线程A先抢到一张票时,寄存器中tickets 10000——>9999, 还未写回内存就被切走执行线程B了;线程B也抢票,直接抢了9950张,还剩50张,此时又切回线程A,又把9999写入内存,就错误了。

(4)解决方案

原子性:一件事要么不做,要么全做完

把tickets--这个临界区设为原子的,使不想被打扰,加锁

3.加锁

(1)加锁介绍

加锁范围:临界区,只要对临界区加锁,而且加锁的粒度约细越好

加锁本质:加锁的本质是让线程执行临界区代码串行化

加锁是一套规范,通过临界区对临界资源进行访问的时候,要加就都要加

锁保护的是临界区, 任何线程执行临界区代码访问临界资源,都必须先申请锁,前提是都必须先看到锁!那这把锁,本身不就也是临界资源吗?锁的设计者早就想到了

pthread_mutex_lock: 竞争和申请锁的过程,就是原子的!申请锁的过程不会中断,不会被打扰。

难度在加锁的临界区里面,就没有线程切换了吗????

(2)定义/释放 互斥锁

man pthread_mutex_init

① pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 定义全局/静态的互斥锁,可以用这个宏初始化

② int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);         mutex:锁的地址。attr:锁的属性设为空
             

③ int pthread_mutex_destroy(pthread_mutex_t *mutex); 释放锁

(3)加锁、解锁

man pthread_mutex_lock

① int pthread_mutex_lock(pthread_mutex_t *mutex); 阻塞式申请的锁

线程1正在用这个阻塞锁,那线程2就要阻塞式等待线程1用完才能申请这个锁

② int pthread_mutex_trylock(pthread_mutex_t *mutex); 非阻塞式申请的锁

线程1正在用这个非阻塞锁,那线程2就直接返回,只有没有别的线程用这个锁,自己才能用

③ int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁

(4)使用锁代码

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h> // 仅仅是了解

// __thread int global_value = 100;

// void *startRoutine(void *args)
// {
//     // pthread_detach(pthread_self());
//     // cout << "线程分离....." << endl;
//     while (true)
//     {
//         // 临界区,不是所有的线程代码都是临界区
//         cout << "thread " << pthread_self() << " global_value: "
//              << global_value << " &global_value: " << &global_value
//              << " Inc: " << global_value++ << " lwp: " << ::syscall(SYS_gettid)<<endl;
//         sleep(1);
//         break;
//     }
//     // 退出进程,任何一个线程调用exit,都表示整个进程退出
//     //exit(1);
//     // pthread_exit()
// }

using namespace std;

// int 票数计数器
// 临界资源
int tickets = 10000; // 临界资源,可能会因为共同访问,可能会造成数据不一致问题。
pthread_mutex_t mutex;

void *getTickets(void *args)
{
    const char *name = static_cast<const char *>(args);

    while (true)
    {
        // 临界区,只要对临界区加锁,而且加锁的粒度约细越好
        // 加锁的本质是让线程执行临界区代码串行化
        // 加锁是一套规范,通过临界区对临界资源进行访问的时候,要加就都要加
        // 锁保护的是临界区, 任何线程执行临界区代码访问临界资源,都必须先申请锁,前提是都必须先看到锁!
        // 这把锁,本身不就也是临界资源吗?锁的设计者早就想到了
        // pthread_mutex_lock: 竞争和申请锁的过程,就是原子的!
        // 难度在加锁的临界区里面,就没有线程切换了吗????
        pthread_mutex_lock(&mutex);
        if (tickets > 0)
        {
            usleep(1000);
            cout << name << " 抢到了票, 票的编号: " << tickets << endl;
            tickets--;
            pthread_mutex_unlock(&mutex);

            //other code
            usleep(123); //模拟其他业务逻辑的执行
        }
        else
        {
            // 票抢到几张,就算没有了呢?0
            cout << name << "] 已经放弃抢票了,因为没有了..." << endl;
            pthread_mutex_unlock(&mutex);
            break;
        }
    }

    return nullptr;
}

// 如何理解exit?
int main()
{
    pthread_mutex_init(&mutex, nullptr);
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");
    pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");
    pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");
    pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");

    // sleep(1);
    // 倾向于:让主线程,分离其他线程

    // pthread_detach(tid1);
    // pthread_detach(tid2);
    // pthread_detach(tid3);

    // 1. 立即分离,延后分离 -- 线程活着 -- 意味着,我们不在关心这个线程的死活。4. 线程退出的第四种方式,延后退出
    // 2. 新线程分离,但是主线程先退出(进程退出) --- 一般我们分离线程,对应的main thread一般不要退出(常驻内存的进程)
    // sleep(1);

    int n = pthread_join(tid1, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid2, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid3, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid4, nullptr);
    cout << n << ":" << strerror(n) << endl;

    pthread_mutex_destroy(&mutex);

    return 0;
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

22-python异常

异常一. 了解异常二. 异常的写法2.1 语法2.2 快速体验2.3 捕获指定异常2.3.1 语法2.3.2 体验2.3.3 捕获多个指定异常2.3.4 捕获异常描述信息2.3.5 捕获所有异常2.4 异常的else2.5 异常的finally三. 异常的传递四. 自定义异常五. 总结一. 了解异常 当检测到一个错误时&#xff…

Hibernate多表的关联关系、懒加载

一、一对多关系&#xff1a;插入&#xff1a; “一”的一方为主表&#xff0c;“多”的一方为副表&#xff0c;主表关联副表&#xff0c;应该在主表中加入副表对象作为属性。 根据顾客ID插入顾客信息 &#xff08;一&#xff09; &#xff0c;同时将顾客名下所有订单插入 &…

Python实现人脸识别检测,对主播进行颜值排行

前言 嗨嗨&#xff0c;我亲爱的家人们 今天来整点不一样的&#xff0c;嘿嘿 用Python简单实现对人脸识别的检测&#xff0c;对某平台主播照片进行评分排名 应该对女主播这个词不陌生吧&#xff0c;怎么说应该还是蛮多人看过一些女主播吧 我无聊的时候也会看看&#xff0c;…

2009年数学二真题复盘

选择题: 间断点的判断的前置芝士: 间断点的定义 设函数f(x)在点的去心领域内有定义,若f(x)满足以下条件之一: 在x=没有定义在x=有定义,但是不存在,或者存在,但是极限值不等于函数值。 类型定义 相关概念第一类间断点

CMS垃圾回收器

概述 CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上&#xff0c;这种垃圾回收器非常适合。在启动JVM参数加上-XX:UseConcMarkSweepGC&#xff0c;这个参数表示对于老年代的回收采用CMS。CMS采用的基础算…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.13 j2cache 相关配置

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.13 j2cache 相关配置5.13.1 j2cache 相关配置5.13…

直播绿幕抠图的例子(绿幕抠图直播实例参考)

阿酷TONY / 2022-11-21 / 长沙 什么是绿幕抠图&#xff1a; 设定绿幕或绿布&#xff0c;做直播软件抠图&#xff0c;这时绿幕绿布就可以被实时的抠掉&#xff0c;绿色就变成透明了&#xff0c;只剩下绿幕外的人物&#xff0c;此时添加上直播的背景画质&#xff0c;就实现了绿…

Git——Git常用命令

目录 常用命令概览 1. 设置用户签名 2. 初始化本地库 2.1 初始化本地库 2.2 查看文件 2.3 查看隐藏文件 2.4 进入到下一个目录 3. 查看本地库状态 4.添加暂存区 4.1 删除文件 5. 提交本地库 5.1 将暂存区的文件提交到本地库 6. 查看版本信息的命令 7.修改文件 8. 历史版本…

【Python入门指北】服务器信息清洗

服务器信息清洗 文章目录服务器信息清洗一、 subprocess 执行本机命令二、 获取服务器的硬件基础信息1. 基础信息2. 厂家和产品信息3. CPU 信息3.1 查看物理CPU型号3.2 查看物理CPU颗数3.3 查看每颗物理 CPU 的核心数4. 内存信息练习内存处理参考代码一、 subprocess 执行本机命…

智云通CRM:如何提前识别哪些客户爱说“不”?

有人说&#xff0c;做业务是最好的锻炼意志力方法&#xff0c;因为做业务的人经常会被客户拒绝甚至会扫地出门。被拒绝时&#xff0c;业务员一定要擦亮眼睛&#xff0c;善于察言观色&#xff0c;洞察客户的心理活动。透过观察了解客户为什么说“不”&#xff0c;客户拒绝情况有…

聚观早报 | 推特临时培训员工应对世界杯;世界杯足球内置传感器

今日要闻&#xff1a;推特临时培训员工应对世界杯;京东靠降本增效实现转亏为盈;世界杯足球内置传感器;艾格重返迪士尼CEO职位;特斯拉明年或开启收购计划 推特临时培训员工应对世界杯 据消息&#xff0c; 2022年世界杯拉开帷幕&#xff0c;推特的使用量即将激增&#xff0c;其维…

陆地卫星(Landsat)计划:50多年的星球档案

陆地卫星计划&#xff1a;陆地卫星1号至陆地卫星9号 1967年&#xff0c;NASA&#xff08;美国国家航空与航天局&#xff09; 提出了“地球资源技术卫星”计划&#xff0c;从此开始了在理论上对地球资源技术卫星系列的可行性研究&#xff0c;于是&#xff0c;陆地卫星 (Landsat…

汽车安全气囊设计?Abaqus/Part特殊建模方法-附案例step-by-step教学

作者 | 邓怡超 Abaqus/Part基于特征的建模功能可以说非常齐全&#xff0c;基本能够满足一般的分析要求&#xff0c;更复杂的模型则可以通过与专业三维建模软件之间的接口来导入&#xff0c;今天要说的是部件的另外一种建模方法。 有一种类型的分析&#xff0c;部件自身的初始…

坚持自学软件测试,半年的辛苦没有白费,不过才拿到10k的offer

找软件测试的工作只用了一周的时间&#xff0c;因为自己的年纪已经25岁&#xff0c;所以在简历上包装了两年的工作经验&#xff0c;但是我学的技术水平自认为还可以&#xff0c;因为我当时自学时用的教程比较有深度。 之所以要转行&#xff0c;我相信做机械工作的朋友都明白&a…

神经网络-前向传播Forward propagation

前向传播Forward propagation 前向传播算法就是&#xff1a; 将上一层的输出作为下一层的输入&#xff0c;并计算下一层的输出&#xff0c;一直到运算到输出层为止 在正式介绍前向传播前&#xff0c;先简单介绍计算图&#xff08;Computational Graph&#xff09;的概念。 yw…

LiDAR 完整指南介绍:激光探测和测距

什么是激光探测和测距 (LiDAR)&#xff1f; LiDAR 的全称是 Light Detection and Ranging (激光探测及测距)&#xff0c;LIDAR 是一种主动测量方式&#xff0c;主要由激光发射部分、接收部分组成、信号处理部分组成&#xff0c;从其名称可以发现 LIDAR 的两个主要基本功能是测…

关于我的家乡网页设计主题题材——梧州14页HTML+CSS网页

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

iOS关于列表布局的几种实现方式小结

式 &#xff0c;功能的要求是最多六行&#xff0c;动态展示。当时想到的方案是&#xff0c;抽象出一个cell,初始化六个标签&#xff0c;动态的控制显示和隐藏&#xff0c;这样功能上没有问题&#xff0c;就是代码有些冗余。请教了身边的美女同事&#xff0c;她那边的思路是用UI…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.17 发送多部件邮件

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.17 发送多部件邮件5.17.1 发送多部件邮件5.17.2 添…

Python脚本之并发执行加密方法【一】

本文为博主原创&#xff0c;未经授权&#xff0c;严禁转载及使用。 本文链接&#xff1a;https://blog.csdn.net/zyooooxie/article/details/125650427 之前写过一篇 JMeter性能测试之参数加密【一】&#xff0c;现在把后面的补上。实际第一篇就写完了 JMeter压测遇到加密接口…