Linux高级编程:进程(三),线程(一)

news2024/12/31 6:47:45

进程的一生:

execute:

exec族
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
新进程,所以调用exec前后该进程的id并未改变。

其实有六种以exec开头的函数,统称exec函数:
vector
ls -l -i list 
execl("/bin/ls","-l","-i",NULL);
execlp("ls","-l","-i",NULL);

        #include <unistd.h>

       int exec l(const char *path, const char *arg, ...);
       int exec lp(const char *file, const char *arg, ...);
       int exec le(const char *path, const char *arg,..., char * const envp[]);
       int exec v(const char *path, char *const argv[]);
       int exec vp(const char *file, char *const argv[]);
       int exec vpe(const char *file, char *const argv[], char *const envp[]);

(1).带l  vs 带 v 


    int execl  (const char *path, const char *arg, ...); //list   ...表示任何可变参数
    int execv  (const char *path, char *const argv[]);//vector ---数组


功能:
            执行一个文件 (可执行文件 a.out / 1.sh )
image 映像/镜像
text|data|bss|堆 栈|环境变量及命令行参数 + pcb
   用新进程的 镜像 替换 调用进程的 镜像    替换之后身份未变,还是bash的子进程
参数:
          @path   要执行的文件的路径(包含可执行文件的名字)
          eg:
              ls       
              /bin/ls 
          @arg   表示 可执行文件的文件名 (命令)

             ls  
             ... 可变参数 
         ls -l / 
         最后写一个NULL 表示结束 

返回值:成功不会返回,失败返回-1.
 execl("/bin/ls","ls","-l","/",NULL);

区别:
    在于,参数传递的方式不同,
    l --- list ---参数逐个列举 
    eg:
     execl("/bin/ls","ls","-l","/",NULL);
    v ---vector --- 参数组织成 指针数组的形式 
    eg:
      char *const arg[] = {"ls","-l","/",NULL};
      execv("/bin/ls",arg);
      

(2). p vs e

int execl p (const char *file, const char *arg, ...);
int execv p (const char *file, char *const argv[]);

p --- path ->PATH (环境变量 --- 都是可执行文件的路径)   //  echo $PATH 查看环境变量

带p 表示可执行文件的寻找方式,是从系统的环境变量PATH中的路径下面去找

如何用execlp/execvp方式启动自己的程序。
        将自己写的程序所在的路径,添加到系统的环境变量中即可。
        export PATH=$PATH:/home/linux/2021-code/3month/2-proc/proc/exec //注意,先引用原有环境变量 再追加

int execl  e (const char *path, const char *arg,...,   char * const envp[]);
int execvp e(const char *file, char *const argv[]  ,   char * const envp[]);

带e 表示的是可以给要执行的 新程序 传递需要的 环境变量 

extern char **environ; //系统的环境变量信息 的指针数组的首地址 

env 可以看环境变量

练习:

实现

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
	char s[100] = {0};

	while(1)
	{
		printf("minishell$ ");
		fgets(s,sizeof(s),stdin);
		s[strlen(s) - 1] = '\0';
		
		if(strncmp(s,"quit",4) == 0 || strncmp(s,"exit",4) == 0)
		{
			printf("-----exit-----\n");
			return 0;
		}

		char *p[10] = {NULL};
		int i = 0;
		p[i] = strtok(s," ");
		while(p[++i] = strtok(NULL," "))
			;	

		pid_t pid = fork();
		if(pid < 0)
		{
			perror("fork fail");
			return -1;
		}
		if(pid > 0)
		{
			int status;
			printf("father -----\n");
			wait(NULL);
		}
		else if(pid == 0)
		{
			if(execvp(p[0],p) < 0);
			{
				perror("execvp fail");
				return -1;
			}
			//exit(99);
		}
	}
	return 0;
}

进程的总结:

线程thread :

并发量的问题(并发程度)

进程的特点:

a.父子进程的独立空间

b.

线程:

区别:

        进程 -是资源分配的基本单位
        线程 -系统调度的最小单位

        线程是CPU执行的最下单位   //就是来干活的

        线程是轻量级的进程,进程是重量级的进程--需要大量的资源的分配

        线程不需要太多的资源

        {       

                 线程pid

                 程序计数器  (pc - process counter)

                 相关寄存器

                 栈的空间

        }

        进程是分配资源和调度执行的基本单位。

        fork
        分配资源 --- 进程 (获取系统系统资源)
        调度执行 --- 线程 (侧重执行任务)

        线程 因为 共享 进程的资源
        避免了 大量资源的开辟
        线程的创建效率 高于进程创建效率

线程的缺点

安全性较差,---

线程间的通信。 ---共享进程资源

关系:        

        1.线程是存在进程中的

        2.线程共用的进行的各个段和相关资源

线程的函数:

NPTL(Native Posix Thread Library) 线程库

1.生命周期:

(1)线程的创建:
pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);
        功能:该函数可以创建指定的一个线程。

        参数:

                  @thread 线程id,需要实现定义并由该函数返回。
                  @attr   线程属性,一般是NULL,表示默认属性。(可结合性+分离属性)
                  @start_routine 
                        指向指针函数的函数指针。
                          本质上是一个函数的名称即可。
                        称为回调函数,是线程的执行空间。
                  @arg  回调函数的参数,即参数3的指针函数参数。
            返回值:  成功 0
                            失败 错误码

练习:创建两个子线程,同时操作1个变量,一个+1,一个+2

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

//线程的执行函数 -回调函数 
void * doSomething1(void *arg)
{
	int i = 0;

	int *p = arg;
	while (1)
	{
		(*p)++;
		printf("1 doSomething n = %d i = %d\n",*p,i++);
		sleep(1);
	}

	return NULL;
}

void *doSomething2(void *arg)
{
	int i = 0;
	int *p = arg;
	while (1)
	{
		printf("2 doSomething n = %d i = %d\n",(*p)+=2,i++);
		sleep(1);
	}
	return NULL;
}

#define N 2

typedef void *(*threadF_t) (void*);
int main(int argc, const char *argv[])
{
	int n = 10;
	
	pthread_t tid[N]; //long 
	///threadF_t pFunc[] = {doSomething1,doSomething2,NULL};
	
	int i = 0;
	for (i = 0; i < N; ++i)
	{
		//int ret = pthread_create(&tid[i],NULL,pFunc[i],&n);
		int ret = pthread_create(&tid[i],NULL,doSomething1,&n);

		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			return -1;

		}
	}
#if 0
	int ret = pthread_create(&tid1,NULL,doSomething1,&n);

	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;

	}
	
	pthread_t tid2; //long 
	ret = pthread_create(&tid2,NULL,doSomething2,&n);

	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;

	}
#endif

	//printf("tid = %ld\n",tid);

	//while(1);
	while (1)
	{
		printf("main n = %d\n",n);
		sleep(1);
	}
	return 0;
}

int pthread_attr_init

int pthread_attr_init(pthread_attr_t *attr);

        功能:

                @初始化一个attr的变量

        参数:

               @ attr,需要变量来接受初始值

        返回:

                0 成功,

                非0 错误;

int pthread_attr_destroy

int pthread_attr_destroy(pthread_attr_t *attr);

        功能:销毁attr变量。

        参数: @attr,属性变量

        返回:

                0 成功,

                非0 错误;

int pthread_attr_setdetachstate

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);(这个风险不大就是恨麻烦)

        功能:把一个线程设置成相应的属性

        参数:@attr,属性变量,有init函数初始化他。

                @detachstate:有2个可选值

              1、PTHREAD_CREATE_DETACHED     可分离 //设置好线程的初始状态,可分离就不用主线程去回收了

              2、PTHREAD_CREATE_JOINABLE       可结合

                设置分离属性。

        返回值:成功 返回 0

                      失败  >0 ,以及错误号

int pthread_detach(pthread_t thread);

这个写在create下面,创建成功下面,但是也有风险,就怕分离前子线程执行完了,才去分离已经没用了,也可以直接放在子线程开头

        功能,设置分离属性

        参数,线程id号,填自己的id

设置为可分离状态时,主线程就可以干自己的事情,不用自己用join来回收(join能获得子线程的状态,关心其回收状态看无人机),由系统来回收

创建线程时就设置了初始状态可分离

(2)线程的执行:

        回调函数------执行任务

(3)线程的结束:     
线程的结束条件:

The new thread terminates in one of the following ways:

       * It  calls  pthread_exit(3),            //pthread_exit([status])
         specifying  an  exit  status  value that is available to
         another thread in the same process that calls pthread_join(3).

       * It returns from start_routine().          //return 
         This is equivalent to calling pthread_exit(3)  with
         the value supplied in the return statement.

       * It is canceled (see pthread_cancel(3)).         //被取消 
       * Any of the threads in the process calls exit(3),        //exit() --- 进程正常结束 
         or the main thread performs a return from main()       .//main- return 意味着就是进程结束。  
         This causes the termination of all threads in the process.

结束的函数 
pthread_exit() 

        void pthread_exit(void *retval);     // 与return 的效果一致  
                功能:子线程自行退出
                参数: retval 线程退出时候的返回状态,临死遗言。
                返回值:无

//若在主线程中调用,表示主线程结束,但是,此时进程空间不销毁,直到,所有的子线程都结束只有,此时进程空间销毁。

        pthread_exit(值所在空间得地址)    这个值可以是数字和字符串

pthread_join()

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

        

void pthread_exit(void *retval); //退出时,将"要传递的退出的状态值"的地址 传递  
int pthread_join(pthread_t thread, void **retval);//等待 接收 pthread_exit()  传递过来的 状态值的地址保存   

pthread_exit pthread_join (类似exit和wait)

pthread_datach(tid)

       int pthread_detach(pthread_t thread);

        将线程分离,不用主线程来join了

pthread_cancel()

强制退出 ==》他杀  ==》主线程结束子线程
        int pthread_cancel(pthread_t thread);
        功能:请求结束一个线程
        参数:thread 请求结束一个线程tid
        返回值:成功 0
                      失败 -1;

若在主线程中调用来结束子线程,则需传入子线程的tid,若在子线程中调用来结束主线程也相同。

练习:用线程  无人机   回收线程

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h> 
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>

#define N 4

void *ctrl(void *arg)
{
	int i = 0;
	while(i < 3)
	{
		printf("---ctrl----\n");
		sleep(1);
		++i;
	}
	pthread_exit("ctrl");
}

void *video(void *arg)
{	
	int i = 0;
	while(i < 4)
	{
		printf("---video----\n");
		sleep(1);
		++i;
	}
	pthread_exit("video");
}

void *trans(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---trans----\n");
		sleep(1);
		++i;
	}
	pthread_exit("trans");
}

void *store(void *arg)
{
	int i = 0;
	while(i < 6)
	{
		printf("---store----\n");
		sleep(1);
		++i;
	}
	pthread_exit("store");
}

typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{

	pthread_t tid[N];
	threadF_t pFunc[] = {ctrl,video,trans,store,NULL};

	int i = 0;
	for(;i < N;++i)
	{
		int ret = pthread_create(&tid[i],NULL,pFunc[i], NULL);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			//return -1;
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}

	}

	void *retval;
	int j = 0;
	for(;j < N;++j)
	{
		pthread_join(tid[j],&retval);
		printf("-----%s  exit------\n",(char *)retval);
	}

	printf("----------------main----exit-----------------\n");
	return 0;
}

void pthread_cleanup_push(void (*routine)(void *), void *arg);

        功能:注册一个线程清理函数(第一个是调一个自己写的清理函数,里面可以写要销毁的东西,比如用来分离的属性,第二个是清理函数要传的参数)

        参数,     @routine,线程清理函数的入口

                        @arg,清理函数的参数。

        返回值,无

void pthread_cleanup_pop(int execute);

        功能:调用清理函数

        @execute,

               非0 执行清理函数

                0,不执行清理

                但是关闭就会触发如果它放在关闭后面,这时候0和1都没关系

        返回值,无

这两个必须一起用他们的本质的宏,各自完成do{(clean_push)

所以这里面在预定义的时候会替换,注意一定要符合语法

}while的一半(clean_pop)

 注:写了pthread_cleanup_push就必须写 pthread_cleanup_pop,否则会报错!。

触发方式:  以下方式都会触发cleanup

        1.pthread_cleanup_pop(非零值)

        2.pthread_cleanup_pop(0)   // pthread_exit()    退出动作会导致 触发

        pthread_exit(NULL); //线程正常结束

        pthread_cleanup_pop(0);  //放在pthread_exit()后,就算是0也会执行清理。

        3.pthread_cancel();: // 线程异常结束   被其他线程结束时

为什么需要cleanup呢?

因为在初始化线程attr参数时,创建了一些资源,线程分离自己回收后,设置attr的资源也要需要清理,故要调用cleanup函数。

练习:

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h> 
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>

#define N 4

void *ctrl(void *arg)
{
	int i = 0;
	while(i < 5)
	{
		printf("---ctrl----\n");
		sleep(1);
		++i;
	}
	pthread_exit("ctrl");
}

void *video(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---video----\n");
		sleep(1);
		++i;
	}
	pthread_exit("video");
}

void *trans(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---trans----\n");
		sleep(1);
		++i;
	}
	pthread_exit("trans");
}

void *store(void *arg)
{
	int i = 0;
	while(i < 5)
	{
		printf("---store----\n");
		sleep(1);
		++i;
	}
	pthread_exit("store");
}

void cleanup(void *arg)
{
	pthread_attr_destroy(arg);
	printf("cleanup\n");
}

typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{

	pthread_t tid[N];
	threadF_t pFunc[] = {ctrl,video,trans,store,NULL};

	int i = 0;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	for(;i < N;++i)
	{
		int ret = pthread_create(&tid[i],&attr,pFunc[i], NULL);  //创建线程时就设置好分离状态
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			//return -1;
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
		//pthread_detach(tid[i]);
	}
	pthread_cleanup_push(cleanup,&attr);	
	while(1)
	{	
		printf("----------------main----exit-----------------\n");
		sleep(1);
	}
	pthread_cleanup_pop(0);
	pthread_exit(NULL);
	return 0;
}

多线程的互斥机制

临界资源: 共享资源

临界区 : 一段代码区域(访问临界资源的那段代码)

原子操作: 要么不操作,要操作,一定是一次完整的操作。不能被打断。

概念:

互斥 ===》在多线程中对临界资源的排他性访问。

互斥机制 ===》互斥锁 ===》保证临界资源的访问控制。

框架:

  定义互斥锁-》初始化锁-》加锁-》解锁-》销毁

1、定义:

pthread_mutex_t mutex;如果定义在main函数要传参,全局大家都能用

2、//初始化一把锁   pthread_mutex_init();   

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

        功能:

                将已经定义好的互斥锁初始化。

        参数:

                @mutex 要初始化的互斥锁

                @atrr

                初始化的值,一般是NULL表示默认锁 //读写锁 ,自旋锁 ()

        返回值:

                成功 0

                失败 非零

3、 //上锁  pthread_mutex_lock();

int pthread_mutex_lock(pthread_mutex_t *mutex);

        功能:用指定的互斥锁开始加锁代码

                加锁后的代码到解锁部分的代码属于原子操作,

                在加锁期间其他进程/线程都不能操作该部分代码

                如果该函数在执行的时候,mutex已经被其他部分

                使用则代码阻塞。

        参数: @mutex 用来给代码加锁的互斥锁

        返回值:成功 0

                        失败 非零

4、//解锁 pthread_mutex_unlock();

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:将指定的互斥锁解锁。

解锁之后代码不再排他访问,一般加锁解锁同时出现。

参数:用来解锁的互斥锁

返回值:成功 0

失败 非零

5、//销毁一把锁 pthread_mutex_destroy();

代码示例:

#include <stdio.h>
#include <pthread.h>
#include <errno.h>

int count;
pthread_mutex_t mutex;
void*doSth1(void *arg)
{
	int i = 0;
	while (i < 50000)
	{
		pthread_mutex_lock(&mutex);
		int temp = count;
		printf("count 1 = %d\n",count);
		count = temp + 1;
		pthread_mutex_unlock(&mutex);
		++i;
	}

	return NULL;
}

void* doSth2(void *arg)
{
	int i = 0;
	while (i < 50000)
	{	
		pthread_mutex_lock(&mutex);
		int temp = count;
        printf("count 2 = %d\n",count);
		count = temp + 1;
		pthread_mutex_unlock(&mutex);
		++i;
	}

	return NULL;
}

int main(int argc, const char *argv[])
{
	pthread_t tid[2];

	pthread_mutex_init(&mutex,NULL); //此时 有了一把锁 
	int ret = pthread_create(&tid[0],NULL,doSth1,NULL);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}
	 ret = pthread_create(&tid[1],NULL,doSth2,NULL);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

练习:

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>

#define N 2

int cnt;
pthread_mutex_t mutex;

void *do_write1(void *arg)
{
	int i = 0;
	while(i < 100)
	{	
		pthread_mutex_lock(&mutex);
		fprintf(arg,"thread1 cnt = %d\n",cnt);
		fflush(arg);
		++i,++cnt;
		pthread_mutex_unlock(&mutex);
	}
}

void *do_write2(void *arg)
{
	int i = 0;
	while(i < 100)
	{
		pthread_mutex_lock(&mutex);
		fprintf(arg,"thread2 cnt = %d\n",cnt);
		fflush(arg);
		++i,++cnt;
		pthread_mutex_unlock(&mutex);
	}
}

void cleanup(void *arg)
{
	pthread_attr_destroy(arg);
	printf("cleanup\n");
}

typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		printf("Usage : %s <filename> ",argv[0]);
		return -1;
	}

	FILE *fp = fopen(argv[1],"w+");	
	
	if(fp == NULL)
	{
		perror("open fail");
		return -1;
	}

	int i = 0;
	pthread_t tid[N];
	threadF_t pFunc[] = {do_write1,do_write2,NULL};

	pthread_mutex_init(&mutex,NULL);

	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	for(i;i < N;++i)
	{
		int ret = pthread_create(&tid[i],&attr,pFunc[i], fp);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
	}
	pthread_mutex_destroy(&mutex);	

	pthread_cleanup_push(cleanup,&attr);	
	pthread_cleanup_pop(1);
	
	pthread_exit(NULL);
	fclose(fp);
	return 0;
}

Tips:

一、断错误处理:

1、gcc  <filename>  -g

2、gdb ./执行文件

3、在gdb下运行  输入r即可。

 

二、获得当前的tid

        pthread_self();

     

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

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

相关文章

Python复合型数据避坑指南

目录 前言 列表&#xff08;Lists&#xff09; 1. 修改可变对象 2. 浅拷贝和深拷贝 元组&#xff08;Tuples&#xff09; 集合&#xff08;Sets&#xff09; 字典&#xff08;Dictionaries&#xff09; 1. 键值唯一性 2. 键的类型 实际应用场景 1. 数据分析与清洗 2. 网络…

微信小程序云开发教程——墨刀原型工具入门(安装以及基础使用教程)

引言 作为一个小白&#xff0c;小北要怎么在短时间内快速学会微信小程序原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;这意味着学习时必须把握微信小程序原型设计中的重点、难点&#xff0c;而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…

Hgame题解(第二星期)

Hgame题解&#xff08;第二星期&#xff09; Web Select More Courses 打开靶机发现是一个登陆页面&#xff0c;根据题目提示下载弱密码字典&#xff0c;通过BP爆破获得用户密码为qwert123 登陆后进入下一个页面&#xff0c;由于学分已满无法选课&#xff0c;所以需要先进行…

仿牛客网项目---显示评论和添加评论功能的实现

这篇文章&#xff0c;我来介绍一下我的项目中的另外一个功能&#xff1a;显示评论和添加评论。 其实这两个功能都不怎么重要&#xff0c;我感觉最重要的应该是用户注册登录功能&#xff0c;这个也了解一下&#xff0c;知道这么一回事儿就好。 首先设计DAO层。 Mapper public …

【刷题】Leetcode 1609.奇偶树

Leetcode 1609.奇偶树 题目描述广度优先搜索&#xff08;BFS&#xff09;深度优先算法&#xff08;DFS&#xff09; 思路一&#xff08;BFS&#xff09;思路二&#xff08;DFS&#xff09;Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&a…

配置之道:深入研究Netty中的Option选项

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 配置之道&#xff1a;深入研究Netty中的Option选项 前言Option的基础概念ChannelOption与Bootstrap Option常见的ChannelOption类型ChannelConfig的使用Option的生命周期不同传输协议的Option 前言 在…

【MySQL】MySQL复合查询--多表查询自连接子查询 - 副本

文章目录 1.基本查询回顾2.多表查询3.自连接4.子查询 4.1单行子查询4.2多行子查询4.3多列子查询4.4在from子句中使用子查询4.5合并查询 4.5.1 union4.5.2 union all 1.基本查询回顾 表的内容如下&#xff1a; mysql> select * from emp; ----------------------------…

Java——建造者模式(Builder)

建造者模式&#xff08;Builder&#xff09; 1、建造者模式的定义 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 Builder模式是一步一步创建一个复杂对象的创建型模式&#xff0c;它允许使用者在不知道内部建造细节的情况下&…

vue3的router

需求 路由组件一般放在&#xff0c;pages或views文件夹, 一般组件通常放在component文件夹 路由的2中写法 子路由 其实就是在News组件里面&#xff0c;再定义一个router-view组件 他的子组件&#xff0c;机会渲染在router-view区域 路由传参 <RouterLink :to"/news…

Charles抓包 - 安装、激活、证书配置

最近刚好又遇到了抓包的需求&#xff0c;之前一直使用 Fiddler 抓包&#xff0c;这几年一直听大家都在用 Charles 抓包&#xff0c;正好一起了解下&#xff08;一般建议掌握一种抓包方式即可&#xff0c;都可以解决同种需求场景&#xff09; 抓包 Fiddler抓包 Charles 下载、安…

Day08:基础入门-算法分析传输加密数据格式密文存储代码混淆逆向保护

目录 传输数据-编码型&加密型等 传输格式-常规&JSON&XML等 密码存储-Web&系统&三方应用 代码混淆-源代码加密&逆向保护 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/I…

HotFix原理学习

原文链接&#xff1a;Unity 游戏用XLua的HotFix实现热更原理揭秘-CSDN博客 本文通过对XLua的HoxFix使用原理的研究揭示出来这样的一套方法。这个方法的 第一步&#xff1a;通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数。第二步&#xff1a;生成函数连接器…

94. 递归实现排列型枚举 刷题笔记

思路 依次枚举 每个位置用哪个数字 要求按照字典序最小来输出 而每次搜索下一层时i都是从1开始 也就是说 如果有小的数可以填上 那么该方案会填上这个数字 例如 当n等于3 第一次搜索 1 2 3输出后返回 返回后此时i3 第二个位置填3 1 3 2 输出后返回 此时返回到第一层…

tkinterFrame框架+标签框架LabelFrame+Toplevel窗口的使用

1.在tkinter中&#xff0c;Frame是一个容器小部件用于组织和管理其他小部件。它可以作为一个独立的可见区域&#xff0c;也可以作为其他小部件的父容器。 import tkinter as tk import tkinter.ttk as ttk import tkinter.messagebox as mbm tk.Tk() m.title("tkinter L…

C 嵌入式系统设计模式 16:循环执行模式

本书的原著为&#xff1a;《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》&#xff0c;讲解的是嵌入式系统设计模式&#xff0c;是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述嵌入式并发和资源管理模式之二…

Linux进程管理:(二)进程调度原语

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 进程调度的概念比较简单&#xff0c…

倒模专用制作耳机壳UV树脂:改性丙烯酸树脂

倒模专用制作耳机壳的UV树脂是经过改性的丙烯酸树脂&#xff0c;具有高透明度、高粘度、快速固化的特点。这种树脂可以通过紫外线光固化&#xff0c;快速形成坚硬的表面&#xff0c;并且具有较高的硬度和耐磨性&#xff0c;因此非常适合用于制作耳机壳。 此外&#xff0c;改性丙…

面试经典 150 题 ---- 轮转数组

面试经典 150 题 ---- 轮转数组 轮转数组方法一&#xff1a;使用额外的数组方法二&#xff1a;数组翻转 轮转数组 方法一&#xff1a;使用额外的数组 我们可以使用额外的数组来将每个元素放至正确的位置。用 n 表示数组的长度&#xff0c;我们遍历原数组&#xff0c;将原数组…

Java SE:多线程(Thread)

1. 线程两个基本概念 并发&#xff1a;即线程交替运行多个指令并行&#xff1a;即多个线程同时运行指令 并发并行不矛盾&#xff0c;两者可同时发生&#xff0c;即多个线程交替运行指令 2. 多线程3种实现方式 2.1 直接创建线程对象 /*** 方式1&#xff1a;* 1. 创建thread类的…

MySQL表分区技术介绍

目录 1. 分区概述 1.1 表分区 1.2 表分区与分表的区别 1.3 表分区的好处 1.4 分区表的限制因素 2. 如何判断当前MySQL是否支持分区&#xff1f; 3. 分区类型详解 3.1 MySQL支持分区类型 3.2 RANGE分区 3.2.1 根据数值范围分区 3.2.2 根据TIMESTAMP范围分区 3.2.3 根…