当前位置: 首页 > news >正文

【Linux】多线程概念线程控制

文章目录

  • 多线程概念
    • Linux下进程和线程的关系
    • pid本质上是轻量级进程id,换句话说,就是线程ID
    • Linux内核是如何创建一个线程的
    • 线程的共享和独有
    • 线程的优缺点
  • 线程控制
    • POSIX线程库
    • 线程创建
    • 线程终止
    • 线程等待
    • 线程分离

多线程概念

Linux下进程和线程的关系

在《程序员的自我修养》这本书中,对Linux下的多线程做了这样的描述:

Windows对进程和线程的实现如同教科书一样标准,Windows内核有明确的线程和进程的概念。在Windows API可以使用明确的API:CreateProcess和CreateThread来创建进程和线程,并且有一系列的API来操纵它们。但对于Linux来说,线程并不是一个通用的概念。
Linux对多线程的支持颇为贫乏,事实上,在Linux内核中并不存在真正意义上的线程的概念。
Linux将所有的执行实体(无论是线程还是进程)都称为任务(Task),每一个任务概念上都类似于一个单线程的进程,具有内存空间、执行实体、文件资源等。不过,Linux下不同的任务之间可以选择共享内存空间,因而在实际意义上,共享了同一个内存空间的多个任务构成了一个进程,这些任务也就成了这个进程里的线程

可以给出以下结论:

  • 线程是依附于进程才能存在的,如果没有进程,则线程不会单独存在
  • 多线程的存在是为了提高整个程序的运行效率的
  • 线程也被称为执行流,一个线程是执行代码的一个执行流,因为线程在执行用户写的代码,程序员创建的线程被称之为“工作线程”
  • Linux内核当中没有线程的概念,只有轻量级进程(LWP),线程是C库中的概念

pid本质上是轻量级进程id,换句话说,就是线程ID

  • 在task_struct结构体当中:
    • pid_t pid:轻量级进程id,也被称为线程id,不同的线程拥有不用的pid
    • pid_t tgid:轻量级进程组id,也被称为进程id,一个进程当中的线程拥有相同的tgid
  • 为什么在进程概念的介绍中,说pid就是进程 id?
    • 线程因为主线程的pid和tgid相等,而我们当时进程中只有一个主线程。所以我们的pid就等于tgid。所以将pid成为进程id也就是现在的tgid。

Linux内核是如何创建一个线程的

其本质就是再在当前进程组中创建一个task_struct结构体,它拥有着和主线程不同的pid,指向同一块虚拟进程地址空间。

在这里插入图片描述

线程的共享和独有

独有:

在进程虚拟地址空间的共享区当中,调用栈,寄存器, 线程ID,errno,信号屏蔽字, 调度优先级独有

  • 调用栈独享
    在这里插入图片描述

  • 寄存器独享:

    当操作系统调度进程的时候一定是以task_struct结构体调度,而task _struc结构体是以双向链表存储,而操作系统调度时是从就绪队列中调度已经就绪的进程,在这里也就是轻量级进程-线程,当调度时一定会有其他线程被切出,而它切出时寄存器中存储的就是当前要执行的指令,所以要用结构体中上下文信息保存

  • 线程ID独享:每个线程就是一个轻量级进程,所以它有自己的pid

  • errno独享:当线程在执行出错时会返回一个errno,这个errno属于当前自己的线程错误

  • 信号屏蔽字独享:阻塞位图

  • 调度优先级独享:每个进程/线程在执行时被调度的优先顺序

共享:

共享:文件描述符表,用户id,用户组id,信号处理方式,当前进程的工作目录

线程的优缺点

优先:

  • 多线程的程序,拥有多个执行流,合理使用(要保证结果运行结构正确,例如多个进程并发执行就可能会出现同时更改一块内存,从而出现运行结果错误,要控制线程的访问时序问题), 可以提高程序的运行效率
  • 多线程程序的线程切换比多进程程序快,付出的代价小 (因为这些线程指向同一个进程虚拟地址空间,有些可以共享的数据,比如:全局变量就能在线程切换的时候,不进行切换可以充分发挥多核CPU并行(并行就是有多个CPU,每个CPU执行一个线程,各自执行各自的)的优势
  • 计算密集型的程序,可以进行拆分,让不同的线程执行计算不一 样的事情(比如我们要从1加到1亿我们可以让多个进程来各自计算其中一段加法,可以更快的得出结果)
  • I/O密集型的程序,可以进行拆分, 让不同的线程执行不同的I/O操作,可以不用串型运行, 提高程序运行效率。比如:我们要从多个文件中读取内容,如果我们只有一个进程的话,那就只能从一个文件中读取之后,在从下一个文件中读取,这样的串行运行,但是当我们有多个进程,就可以让多个进程从多个文件中同时读取。但也不是所有问题都可以拆分成多个进程去分开解决,一个女人花十个月可以生出一个孩子,但是十个女人不能再一个月生出一个孩子(《程序员的自我修养》)
    再比如:scanf是一个阻塞函数,假如printf函数前面有scanf需要被执行,这样,在scanf没有完成的时候,就不能往下执行printf,但是我们让两个线程来分别来执行scanf和printf,这样,就不存在被scanf阻塞,而后面的程序无法执行的问题了
    在这里插入图片描述

缺点:

  • 编写代码的难度更加高(当多个线程访问同一个程序的时候我们需要控制线程访问的先后顺序,要不然就可能出现问题)
  • 代码的(稳定性)鲁棒性要求更加高,一个线程崩溃,会导致整个进程退出。当多个线程在运行时,而CPU资源少的情况下一定是有一线程访问不到CPU资源的,那这时就一定要有线程被切换出来,将CPU资源让出来,这时一旦有线程霸占CPU资源占着不放的话,此时这个得不到CPU资源的线程就有可能崩溃,一旦它崩溃就会导致整个进程退出
  • 线程数量并不是越多越好,线程的切换是需要时间的,所以一个程序的线程数量一定是我们依照一个机器的配置(CPU数量)而经过测量来得出,创建多少个线程合适

在这里插入图片描述

  • 缺乏访问控制,多个线程同时访问一个空间,如果不加以控制,可能会导致程序产生二义性结果

线程控制

POSIX线程库

  • 线程相关的函数构成的函数库,绝大多数函数是以pthread_开头的
  • 使用这些线程函数需要引入头文件pthread.h
  • 编译含有多线程函数的源文件时,要加上编译命令-lpthread选项

线程创建

函数:

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

参数:

  • thread:线程标识符,是一个输出型参数,本质上是线程独有空间的首地址
  • attr:线程的属性信息,一般不设置属性信息,传递NULL,采用默认的线程属性;如果要设置属性信息,一般我们关心下列属性信息:调用栈的大小、分离属性、调度策略、分时策略、调度优先级等等
  • start_routine:函数指针,线程执行的入口函数(线程执行起来的时候,从该函数开始运行,注意:不是从main函数开始运行),当前线程执行时都是从线程入口函数开始执行
  • arg:传递给线程入口函数的参数,也就是给start_routine传递参数
    返回值:
  • 成功返回0
  • 失败返回<0
    线程创建代码演示:
    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* mythread_start(void* arg){6   printf("I am work thread\n");
W>  7 }8 9 int main(){10   pthread_t tid;11   int ret = pthread_create(&tid, NULL, mythread_start, NULL);                                12   if(ret < 0){13     perror("pthread_create");14     return 0;15   }16   return 0;17 }

注意在makefile文件中链接线程库

执行结果:

在这里插入图片描述

很遗憾我们没看到应该存在的输出,这是什么原因呢?

因为线程都是需要操作系统进行调度的,我们在main函数中创建出来一个线程,但是我们的线程还没被调度,main线程就执行结束返回了,main函数返回就意味着进程结束了,进程结束了我们的线程就不存在了,自然不会再给出任何打印。

那我们想看到现象要怎么做呢?很容易想到,让main函数晚一点退出,给工作线程充足的时间能被操作系统调度,我们让main函数在返回前执行sleep(2);

再执行:可以看到工作线程执行了它的代码

为了观察一下线程堆栈和函数堆栈,我们索性让main函数和线程入口函数都进入睡眠状态,修改后代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* mythread_start(void* arg){6   while(1){7     sleep(1);8     printf("I am work thread\n");9   }10 }11 12 int main(){13   pthread_t tid;14   int ret = pthread_create(&tid, NULL, mythread_start, NULL);15   if(ret < 0){16     perror("pthread_create");17     return 0;18   }19   while(1){20     sleep(1);                                                                                21   }22   return 0;23 }

我们看看此时的调用堆栈:

在这里插入图片描述
可以用top -H -p [pid]查看进程状态信息;

我们试着给线程传递一下局部变量,代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 5 void* mythread_start(void* arg){6   int* i = (int*)arg;7   printf("I am work thread %d\n", *i);
W>  8 }9 10 int main(){11   pthread_t tid;12   for(int i=0; i<5; ++i){13     int ret = pthread_create(&tid, NULL, mythread_start, (void*)&i);               14     if(ret < 0){15       perror("pthread_create");16       return 0;17     }18   }19   sleep(1);20   return 0;21 }

观察一下程序执行结果:

在这里插入图片描述

我们的预期是打印0-4的数字,但是执行几次发现,首先每次执行结果并不一样,其次并不按照我们预期的结果进行打印,这是怎么回事呢?是因为线程是抢占式执行的,可能是我们将所有的线程创建出来,再去执行线程的代码,或者说一边创建一边执行代码, 线程的执行顺序不确定,某个线程访问数据的时间也不确定,导致了我们上述那么多种执行结果,还有一种结果是访问数据5,i是我们for循环种的局部变量,如果for循环退出后还有线程去访问i,这是十分危险的,因为i已经被释放了,此时再对它进行访问,就有可能导致错误。

解决上面的方式有两种一种是在main函数中创建一个变量,只要main函数存在,其他那个变量就存在。而main函数退出线程也就退出了,不存在非法访问。这种是解决非法访问的问题。

另一种方式是在堆上申请空间,这样保证每个进程访问的数据是自己对应的数据

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 #include<stdlib.h>5 6 void* mythread_start(void* arg){7   int* p = (int*)arg;8   printf("I am work thread%d\n", *p);                                              9   free(p);
W> 10 }11 12 int main(){13   pthread_t tid;14   for(int i=0; i<5; i++){15     int *p = (int*)malloc(sizeof(int));16     *p = i;17     int ret = pthread_create(&tid, NULL, mythread_start, (void*)p);18     if(ret < 0){19       perror("pthread_create");20       return 0;21     }22   }23   sleep(1);24   return 0;25 }

执行结果:

在这里插入图片描述

总结:

  • 不要给线程传递临时变量,因为传递临时变量当临时变量销毁时,线程拿到的是临时变量的地址,还可以访问那块被释放的空间,容易造成进程崩溃
  • 线程入口函数传递参数的时候,传递堆区空间,释放堆区空间的时候,让线程自己释放

线程终止

线程终止的方法:

1、从线程入口函数种return返回
2、pthread_exit(void* retval)函数,retval:线程退出时, 传递给等待线程的退出信息;作用:
谁调用谁退出,主线程调用主线程终止,工作线程调用工作线程终止
3、pthread_cancel(pthread_t)参数是一个线程标识符,想要取消哪个线程,就传递哪个线程的标识符

补充一个函数:pthread_t pthread_self(void):返回调用此函数的线程id

代码演示:

  • 创建一个线程,然后在main函数中终止这个线程,为了防止是进程结束,而导致线程也结束我们在main函数中加一个死循环

代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* pthread_start(void *arg){6   printf("I am work pthread\n");7   while(1){8     sleep(1);9     printf("I am work thread-l\n");10   }11 }12 int main(){13   pthread_t tid;14   int ret = pthread_create(&tid, NULL, pthread_start, NULL);15   if(ret < 0){16     perror("pthread_create");17     return 0;18   }19   pthread_cancel(tid);20   while(1){21     sleep(1);22     printf("I am main pthread\n");23   }24   return 0;25 } 

执行结果:

在这里插入图片描述

能看到线程并没有立即终止,而是执行了一下线程种的命令然后才终止

  • 观察结束主线程,而不结束工作线程,会出现什么现象

代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* pthread_start(void *arg){6   printf("I am work pthread\n");7   while(1){                       8     sleep(1);                     9     printf("I am work thread-l\n");10   }                               11 }                                 12 int main(){                       13   pthread_t tid;                  14   int ret = pthread_create(&tid, NULL, pthread_start, NULL);15   if(ret < 0){                    16     perror("pthread_create");     17     return 0;                     18   }                               19   getchar();//设置阻塞,当接受到字符后主线程将结束                                      20   pthread_cancel(pthread_self());//结束主线程         21   while(1){                    22     sleep(1);                  23     printf("I am main pthread\n");24   }                            25   return 0;26 }

设置阻塞的目的是为了查看进程id,以观察进程

执行结果:

getchar之前的状态:

在这里插入图片描述
getchar之后的状态:

在这里插入图片描述
用ps aux | grep tex查看前后对比:

在这里插入图片描述

可以得出结论:主线程先退出,工作线程没退出,主线程变成僵尸进程

  • 验证pthread_cancle函数,结束一个线程时,它会执行下一行命令

代码思路:将while循环去掉,让线程退出的下一句代码是return 0,观察程序状况

代码如下:

    2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* pthread_start(void *arg){6   printf("I am work pthread\n");7   while(1){8     sleep(1);9     printf("I am work thread-l\n");10   }11 }12 int main(){13   pthread_t tid;14   int ret = pthread_create(&tid, NULL, pthread_start, NULL);15   if(ret < 0){16     perror("pthread_create");17     return 0;18   }19   getchar();//设置阻塞,当接受到字符后主线程将结束20   pthread_cancel(pthread_self());//结束主线程21   //while(1){22   //  sleep(1);23   //  printf("I am main pthread\n");24   //}                                                                                     25   return 0;26 }

执行结果:

在这里插入图片描述

可以发现这次进程直接退出了,主线程也不是僵尸状态了,这时因为当我们执行pthread_cancle函数时,结束一个线程时,他会执行下一行命令,这时我们将主线程退出了,它在退出前执行了return 0,就会使得整个进程结束,那么此时工作线程也就退出了

  • 观察主线程先退出变成僵尸进程后,工作线程执行完后主线程的状态

代码思路:让主线程退出,然后工作线程等待10s之后退出

代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* pthread_start(void *arg){6   int count = 30;7   while(count--){8     sleep(1);9     printf("I am work thread-l\n");10   }
W> 11 }12 int main(){                                                                                      13   pthread_t tid;14   int ret = pthread_create(&tid, NULL, pthread_start, NULL);15   if(ret < 0){16     perror("pthread_create");17     return 0;18   }19   //getchar();//设置阻塞,当接受到字符后主线程将结束20   pthread_cancel(pthread_self());//结束主线程21   while(1){22     sleep(1);23     printf("I am main pthread\n");24   }25   return 0;26 }

执行结果分析:

在这里插入图片描述

当主线程退出而工作线程不退出时,我们是无法看到进程的调用栈信息的

在这里插入图片描述
总结:

  • 当我们执行pthread_cancle函数时,结束一个线程时,他会执行pthread_cancle函数下一行命令,然后再结束线程
  • 当主线程退出后,工作线程如果依然在执行,主线程就会处于僵尸状态,而当工作线程执行完毕之后退出,整个进程也随之结束

线程等待

线程在创建出来的时候,属性默认是joinable属性,意味着线程在退出的时候需要其他执行流(线程)来回收线程的资源(主要是退出线程使用到的共享区当中的空间)

接口:

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

功能:若线程A调用了该函数等待B线程,A线程会阻塞,直到B线程退出后,A线程才会解除阻塞状态
参数:

  • pthread_t : 线程标识符,要等待哪一个线程,就传递哪个线程的标识符
  • retval : 保存的是一个常数,退出线程的退出信息
线程退出方式*retval保存的东西
return入口函数返回值
pthread_exit函数pthread_exit函数参数
pthread_cancel函数PTHREAD_CANCEL宏定义

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

代码思路:让工作线程等待30s退出,然后在主线程中等待工作线程退出

代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 5 void* pthread_start(void *arg){6   int count = 30;7   while(count--){8     sleep(1);9     printf("I am work thread-l\n");10   }11 }12 int main(){13   pthread_t tid;14   int ret = pthread_create(&tid, NULL, pthread_start, NULL);15   if(ret < 0){16     perror("pthread_create");17     return 0;18   }19   pthread_join(tid, NULL);                                                                       20   return 0;21 }

执行分析:

在这里插入图片描述

线程分离

分离线程是将线程标记成已分离,其属性从joinable变成detach,对于detach属性的线程终止后,系统会自动回收其资源,不用任何线程回收其资源

接口:

int pthread_detach(pthread_t thread);

功能:将线程标记为已分离,目的是当分离的线程终止时,其资源会自动释放,防止产生僵尸进程,防止内存泄漏

参数pthread_t:需要标记分离的线程标识符

调用pthread_detach函数的位置可以是:

  • 在主线程中调用分离创建出来的线程,即主线程标记分离工作线程;
  • 在工作线程的线程入口函数中调用,即自己标记分离自己;
    线程分离的实质就是将线程的属性设置为detach
  • 工作线程退出,然后不回收它的退出状态信息

代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* pthread_start(void *arg){6   int count = 10;7   while(count--){8     sleep(1);9     printf("I am work thread-l\n");10   }
W> 11 }12 int main(){13   pthread_t tid;14   int ret = pthread_create(&tid, NULL, pthread_start, NULL);15   if(ret < 0){16     perror("pthread_create");17     return 0;18   }19   //pthread_join(tid, NULL);20   while(1){21     sleep(1);22   }23   return 0;24 }

执行结果分析:

在这里插入图片描述

可以看到它运行完直接退出了,也没有变成僵尸状态

  • 将工作线程设置为分离状态,观察

代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 
W>  5 void* pthread_start(void *arg){6   pthread_detach(pthread_self());                                                                7   int count = 30;8   while(count--){9     sleep(1);10     printf("I am work thread-l\n");11   }
W> 12 }13 int main(){14   pthread_t tid;15   int ret = pthread_create(&tid, NULL, pthread_start, NULL);16   if(ret < 0){17     perror("pthread_create");18     return 0;19   }20   //pthread_join(tid, NULL);21   while(1){22     sleep(1);23   }24   return 0;25 }

执行分析:

在这里插入图片描述
在这里插入图片描述

结论:无论其他线程等待不等待工作线程退出,回收它的退出状态信息,工作线程都不会变为僵尸状态。

相关文章:

【Linux】多线程概念线程控制

文章目录 多线程概念Linux下进程和线程的关系pid本质上是轻量级进程id&#xff0c;换句话说&#xff0c;就是线程IDLinux内核是如何创建一个线程的线程的共享和独有线程的优缺点 线程控制POSIX线程库线程创建线程终止线程等待线程分离 多线程概念 Linux下进程和线程的关系 在…...

Qt --- 自定义提示框 类似QMessagebox

QMessageBox::information(NULL, QString("title"), QString("I am information")); 以下是自定义提示框的代码&#xff0c;有图有真相&#xff01;提示框大部分都采用模态的形式&#xff0c;关于模态也不再多提&#xff01;所以父类为QDialog&#xff0c;…...

Redis 分布式锁与 Redlock 算法实现

Redis 分布式锁与 Redlock 算法实现 一、简介1. Redis的分布式锁2. 分布式锁的实现原理 二、Redis 分布式锁使用场景1. 分布式系统中数据资源的互斥访问2. 分布式环境中多个节点之间的协作3. 常见场景及应用 三、Redlock算法的原理与实现1. Redlock算法的背景2. Redlock算法的原…...

【附安装包】Inventor2024安装教程 机械制图|三维制图

软件下载 软件&#xff1a;Inventor版本&#xff1a;2024语言&#xff1a;简体中文大小&#xff1a;5.61G安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存8G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu…...

c++ 判断基类指针指向的真实对象类型

在 c 面向对象使用中&#xff0c;我们常常会定义一个基类类型的指针&#xff0c;在运行过程中&#xff0c;这个指针可能指向一个基类类型的对象&#xff0c;也可能指向的是其子类类型的对象&#xff0c;那现在问题来了&#xff0c;我们如何去判断这个指针到底执行了一个什么类型…...

退出屏保前玩一把游戏吧!webBrowser中网页如何调用.NET方法

本文主要以 HackerScreenSaver 新功能的开发经历介绍 webBrowser中网页如何调用.NET方法的过程。 1. 背景 之前开源了一款名为 HackerScreenSaver 的 Windows 屏保程序。该程序具有模拟黑客炫酷界面的特点&#xff0c;用户可以将自定义的网页作为锁屏界面。不久前&#xff0c;…...

hive-列转行

转成 select customer_code,product_type from temp.temp_xx LATERAL VIEW explode(SPLIT(product_types,,)) table_tmp AS product_type where customer_code K100515182...

【网络】IP网络层和数据链路层

IP协议详解 1.概念 1.1 四层模型 应用层&#xff1a;解决如何传输数据&#xff08;依照什么格式/协议处理数据&#xff09;的问题传输层&#xff1a;解决可靠性问题网络层&#xff1a;数据往哪里传&#xff0c;怎么找到目标主机数据链路层&#xff08;物理层&#xff09;&…...

基于Spring Gateway路由判断器实现各种灰度发布场景

文章目录 1、灰度发布实现1.1 按随机用户的流量百分比实现灰度1.2 按人群划分实现的灰度1.2.1 通过Header信息实现灰度1.2.2 通过Query信息实现灰度1.2.3 通过RemoteAdd判断来源IP实现灰度 2、路由判断器2.1. After2.2. Before2.3. Between2.4. Cookie2.5. Header2.6. Host2.7.…...

mysql57、mysql80 目录结构 之 Windows

查看mysql 数据存储的位置 /bin&#xff1a;存储可执行文件&#xff0c;主要包含客户端和服务端启动程序&#xff0c;如mysql.exe、mysqld.exe等 /docs&#xff1a;存放一些文档 /include&#xff1a;用于放置一些头文件&#xff0c;如&#xff1a;mysql.h、mysqld_error.h 等 …...

Mac操作系统Safari 17全新升级:秋季推出全部特性

苹果的内置浏览器可能是Mac上最常用的应用程序&#xff08;是的&#xff0c;甚至比Finder、超级Mac Geeks还要多&#xff09;。因此&#xff0c;苹果总是为其浏览器Safari添加有用的新功能。在今年秋天与macOS Sonoma一起推出的第17版中&#xff0c;Safari可以帮助你提高工作效…...

UDP通信、本地套接字

#include <sys/types.h> #include <sys/socket > ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);- 参数&#xff1a;- sockfd : 通信的fd- buf : 要发送的数据- len : 发送数据的长度…...

ChatGPT提示与技巧分享:如何作出更好的提示2023年8月

​对ChatGPT的一些酷炫技巧感兴趣吗?这里提供了一些可以帮助你充分利用ChatGPT&#xff0c;成为AI工具专家的技巧。 毫无疑问&#xff0c;ChatGPT是目前最广泛使用的人工智能工具之一。它不仅毫不留情地取代了一些特定领域常用的软件小工具&#xff08;如智能对联、经典语录生…...

网络安全(自学黑客)一文全解

目录 特别声明&#xff1a;&#xff08;文末附资料笔记工具&#xff09; 一、前言 二、定义 三、分类 1.白帽黑客&#xff08;White Hat Hacker&#xff09; 2.黑帽黑客&#xff08;Black Hat Hacker&#xff09; 3.灰帽黑客&#xff08;Gray Hat Hacker&#xff09; 四…...

Vue中ElementUI结合transform使用时,发现弹框定位不准确问题

在近期开发中&#xff0c;需要将1920*1080放到更大像素大屏上演示&#xff0c;所以需要使用到transform来对页面进行缩放&#xff0c;但是此时发现弹框定位出错问题&#xff0c;无法准备定位到实际位置。 查看element-ui官方文档无果后&#xff0c;打算更换新的框架进行开发&am…...

(一)连续随机量的生成-基于分布函数

连续随机量的生成-基于分布函数 1. 概率积分变换方法&#xff08;分布函数&#xff09;2. Python编程实现指数分布的采样 1. 概率积分变换方法&#xff08;分布函数&#xff09; Consider drawing a random quantity X X X from a continuous probability distribution with …...

【springboot】Spring Cache缓存:

文章目录 一、导入Maven依赖&#xff1a;二、实现思路&#xff1a;三、代码开发&#xff1a; 一、导入Maven依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><…...

数学建模-建模算法(4)

python虽然不是完全为数学建模而生的&#xff0c;但是它完整的库让它越来越适合建模了。 - 线性规划&#xff1a;使用scipy.optimize.linprog()函数 python from scipy.optimize import linprogc [-1, 4] A [[-3, 1], [1, 2]] b [6, 4] x0_bounds (None, None) x1_bound…...

python之函数返回数据框

1.原始文件 ##gff-version 3 Chr1A IWGSC_v2.1 gene 40098 70338 33 - . IDTraesCS1A03G0000200;previous_idTraesCS1A02G000100;primconfHC;NameTraesCS1A03G0000200;cdsCDS_OK;mappingfullMatchWithMissmatches Chr1A IWGSC_v2.1 mRN…...

电子商务安全体系架构技术方面

技术方面是本文所要阐述的主要方面&#xff0c;因为它能够依靠企业自 身的努力来达到令人满意的安全保障效果。目前&#xff0c;关于电子商务安全体系的研究比 较多&#xff0c;有基于层次的体系&#xff0c;也有基于对象的体系&#xff0c;还有基于风险管理的体系&#xff0…...

新安装IDEA 常用插件、设置

新安装IDEA 常用插件、设置 mybatiscodeHelperProRestfulToolkit-fixJrebelmybatis log freepojo to jsonGrep ConsoleMaven HelperCamelCaseCamelCase常用设置 mybatiscodeHelperPro mapper.xml 编码校验 sql 生成&#xff0c;代码生成 RestfulToolkit-fix URI 跳转到对应的…...

ChromeOS 的 Linux 操作系统和 Chrome 浏览器分离

导读科技媒体 Ars Technica 报道称&#xff0c;谷歌正在将 ChromeOS 的浏览器从操作系统中分离出来 —— 让它变得更像 Linux。虽然目前还没有任何官方消息&#xff0c;但这项变化可能会在本月的版本更新中推出。 据介绍&#xff0c;谷歌将该项目命名为 "Lacros"——…...

哔哩哔哩 B站 bilibili 视频倍速设置 视频倍速可自定义

目录 一、复制如下代码 二、在B站视频播放页面进入控制台 三、将复制的代码粘贴到下方输入框&#xff0c;并 回车Enter 即可 四、然后就可以了 一、复制如下代码 &#xff08;该代码用于设置倍速为3&#xff0c;最后的数值是多少就是多少倍速&#xff0c;可以带小数点&#…...

Lazada商品详情接口 获取Lazada商品详情数据 Lazada商品价格接

一、引言 随着电子商务的迅速发展和普及&#xff0c;电商平台之间的竞争也日趋激烈。为了提供更好的用户体验和更高效的后端管理&#xff0c;Lazada作为东南亚最大的电商平台之一&#xff0c;开发了一种商品详情接口&#xff08;Product Detail API&#xff09;。该接口允许第…...

路由攻击(ospf attack)及C/C++代码实现

开放式最短路径优先&#xff08;OSPF&#xff09;是应用最广泛的域内路由协议之一。不幸的是&#xff0c;它有许多严重的安全问题。OSPF上的伪造是可能导致路由循环和黑洞的最关键的漏洞之一。 大多数已知的OSPF攻击基于伪造攻击者控制的路由器的链路状态通告&#xff08;LSA&…...

nginx配置站点强制开启https

当站点域名配置完SSL证书后&#xff0c;如果要强制开启HTTPS&#xff0c;可以在站点配置文件中加上&#xff1a; #HTTP_TO_HTTPS_START if ($server_port !~ 443){rewrite ^(/.*)$ https://$host$1 permanent; } #HTTP_TO_HTTPS_END 附上完整的配置完SSL证书&#xff0c;强制…...

Jacoco XML 解析

1 XML解析器对比 1. DOM解析器&#xff1a; ○ 优点&#xff1a;易于使用&#xff0c;提供完整的文档树&#xff0c;可以方便地修改和遍历XML文档。 ○ 缺点&#xff1a;对大型文档消耗内存较多&#xff0c;加载整个文档可能会变慢。 ○ 适用场景&#xff1a;适合小型XML文档…...

【面试题】JDK(工具包)、JRE(运行环境和基础库)、JVM(java虚拟机)之间的关系?

【面试题】JDK、JRE、JVM之间的关系&#xff1f; JDK(Java Development Kit):Java开发工具包&#xff0c;提供给Java程序员使用&#xff0c;包含了JRE&#xff0c;同时还包含了编译器javac与自带的调试工具Jconsole、jstack等。 JRE(Java Runtime Environment):Java运行时环境&…...

软件设计师学习笔记7-输入输出技术+总线+可靠性+性能指标

目录 1.输入输出技术 1.1数据传输控制方式 1.2中断处理过程 2.总线 3.可靠性 3.1可靠性指标 3.2串联系统与并联系统 3.3混合模型 4.性能指标 1.输入输出技术 即CPU控制主存与外设交互的过程 1.1数据传输控制方式 (1)程序控制&#xff08;查询&#xff09;方式&…...

Windows下MATLAB调用Python函数操作说明

MATLAB与Python版本的兼容 具体可参看MATLAB与Python版本的兼容 操作说明 操作说明请参看下面两个链接&#xff1a; 操作指南 简单说明&#xff1a; 我安装的是MATLAB2022a和Python3.8.6&#xff08;安装时请勾选所有可以勾选的&#xff0c;包括路径&#xff09;。对应版本安…...

企业网站管理系统程序名称/竞价排名规则

简单的并行加速计算&#xff08;python&#xff09; backend&#xff1a;用于设置并行方式&#xff0c; 多进程方式有’loky’&#xff08;更稳定&#xff09;和’multiprocessing’两种可选项&#xff0c; 多线程方式有’threading’一种选项。默认为’loky’ n_jobs&#x…...

网站推广文章范例/谷歌关键词推广怎么做

题意&#xff1a;http://www.lightoj.com/volume_showproblem.php?problem1140 就是让你去查找L到R中0 的个数 但是不含有前导0 #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #include<map&…...

重庆网站优化指导/成都网站快速优化排名

Windows消息被广泛用于各种事件的通知上面,如果你想操作窗口或者控件(UI元素其实也是一种窗口,如:按钮,编辑框,工具栏,树形控件等)的话,给它发送消息即可。消息也可以来至于其他应用程序。你也可以通过消息来实现系统通知,移动鼠标,按下键盘上的某键等操作。 正如我们前面所讨…...

宽屏企业网站源码/免费推广论坛

1.创建拦截器基类/*** 抽象类* 任何要实现拦截器都需要继承自这个类&#xff0c;并实现其中的interceptor方法&#xff0c;并添加至拦截器池中&#xff0c;就可以实现拦截功能*/Ext.define(system.interceptor.BaseInterceptor,{alternateClassName:[system.BaseInterceptor],s…...

织梦网站搬家教程/广州网站建设方案维护

题目&#xff1a; 我是超链接 题解&#xff1a; 看上去像网络瘤题&#xff1f;嗯很明显想多了&#xff0c;这可是一棵树啊 然后陷入一脸不可做的状态。。 翻翻翻….. 以某一个点为根的子树&#xff0c;这个点只有两种状态&#xff1a;拐弯&#xff08;两条简单路径在一个…...

儿童编程网课平台哪个好/seo优化技术教程

AtomicReference类的getAndSet()方法用于以原子方式将AtomicReference对象的值设置为newValue&#xff0c;该值作为参数传递并返回AtomicReference对象的旧值&#xff0c;并具有由VarHandle.getAndSet(java.lang.Object ... ).VarHandle.getAndSet(java.lang.Object…)指定将变…...