0803|IO进程线程day6 【线程】概念+相关函数

news2024/12/23 10:00:49

一、线程的概念

1.1 什么是线程?

1)线程是一个进程并发执行多种任务的机制。

并发:

  • 单核cpu多个任务同时运行。cpu以ms级别的速度进程进程调度,切换进程和线程。

串行、并发、并行:

  • 并行:多个任务在多核CPU上运行。任务1运行在一个核上,任务2运行在另外一个核上。
  • 并发:多个任务,在单核cpu上运行,同一个时间片上只能运行一个任务。时间片结束后,不管任务是否结束,都会切到下一个任务。
  • 串行:多个任务有序执行,必须等第一个任务结束后才能执行第二个任务。

2) 线程的上下文切换

  • 上下文:运行一个进程所需要的所有资源。
  • 上下文切换:从访问进程1,到访问进程2。cpu访问的资源要替换原有内容,这个操作是一个耗时操作。
  • 为了提高系统性能,引入了一个轻量级的进程的概念,称之为线程。

3)线程属于进程,每一个进程都至少有一个线程作为指令执行体,线程运行在进程空间内。一个进程中可以运行有多个线程,称之为多线程。

1.2 线程是任务运行的最小单位(重点)

同一个进程下的线程,共享该进程的所有资源

  1. 静态存储区(.txt .rodata .data .bss)
  2. 堆区
  3. 栈区
  4. 文件描述符表

1.3 进程和线程的区别(重点!!!!!)

二、线程相关的函数

man手册查看线程相关函数时:Compile and link with -pthread.

有关线程的程序编译时需要家 -pthread

例:gcc 1.c -pthread

2.1 pthread_create

功能:创建一个线程;

原型:

    #include <pthread.h>

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

参数:

    pthread_t *thread:存储创建后的线程的tid号;
    pthread_attr_t *attr:线程属性; 填NULL,代表默认属性。或者用pthread_attr_init(3)初始化线程属性后传参进入:分离属性。
    void *(*start_routine) (void *):回调函数,函数指针,该函数指针指向线程执行体。
    该指针可以指向返回值是void*类型,参数列表是void*类型的函数,例如:
    void* handler(void* arg){                                                                                    
    }
    void *arg:传递给回调函数的参数;

返回值:

        成功,返回0;

        失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;

注意:

  1. 从main函数进来的线程称之为主线程,pthread_create创建的线程称之为分支线程或者子线程。
  2. 一般来说主线程会先运行,但是还是要依照时间片轮询机制。
  3. 主线程退出后(main函数结束),会导致进程结束,依附于该进程内的线程均会被强制退出。
  4. 其他线程退出后,会不会影响到主线程。

2.2 线程的传参

1. 定义一个全局变量int a=10,主线程和分支线程能否访问到?

   访问到的是否是同一份资源?

  • 答:均能访问到,且是同一份资源

2. 在主线程定一个局部变量int b=10,分支线程能否访问到?

  • 答:不能,局部变量作用域在定义他的函数内部

3. 在分支线程定义一个局部变量int c=10,主线程能否访问到?

  • 答:不能,局部变量作用域在定义他的函数内部

4. 若访问不到,用什么方式可以让对方线程访问到。

  • 如下(主线程传参给分支线程)(分支线程传参给主线程)

1)主线程传参给分支线程

2)分支线程传参给主线程

2.3 pthread_exit

 功能:退出分支线程,并传递线程退出状态值;

原型:

    #include <pthread.h>

    void pthread_exit(void *retval);

参数:

        void *retval:指定要传递给主线程的状态值,如果不想传递,填NULL;

                             传递的线程退出状态值被pthread_join函数接收;

PS:

        当分支线程退出后,会残留一部分资源,例如线程的tid号,线程调度块等等,若不回收会出现类似僵尸线程的状态;

        需要使用pthread_join等函数回收;

2.4 pthread_join

 功能:阻塞函数,阻塞等待指定的分支线程退出,并接收分支线程退出状态值,同时回收分支线程的资源;

原型:

       #include <pthread.h>

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

参数:

    pthread_t thread:指定要等待哪个线程,填对应的tid号;
    void **retval:若retval不为空,则该函数会将线程退出状态值(void* retval)拷贝到该二级指针指向的一级指针的内存空间中,若不想接收填NULL; 
    If retval is not NULL, then pthread_join() copies the exit status of the target thread into the location pointed to by retval.

返回值:

        成功,返回0;

         失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
//线程的执行体
void* callback(void* arg)   //void* arg = (void*)&c
{
    int i=0;
    while(i<3)
    {
        printf("this is other func __%d__\n",__LINE__);
        sleep(1);
        i++;
    }

    static int a = 10;  //利用static延长生命周期,否则callback结束,a的值被释放
    printf("&a = %p\n",&a);
    static char str[]="hello world";
    printf("准备退出分支线程... ...\n");
    pthread_exit(str);  //void* retval = str;
}
int main(int argc, const char *argv[])
{
    pthread_t tid;
    if(pthread_create(&tid,NULL,callback,NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n",__LINE__);
        return -1;
    }
    printf("this is main func __%d__\n",__LINE__);                                  

    //线程的退出状态值会被拷贝到&pret指向的一级指针(pret)中
    //即pret = &a;
    void* pret = NULL;
    pthread_join(tid,&pret);  //阻塞等待tid分支线程退出
    printf("*pret = %s %p\n",(char*)pret,pret);

    printf("主线程准备退出\n");

    return 0;
}

练习:创建两个线程:其中一个线程拷贝前半部分,另一个线程拷贝后半部分。

 方法一:

        先在主函数创建并清空拷贝的目标文件,再创建两个线程,在两个线程内部同时打开要读取的文件以及要拷贝的目标文件(两个线程不共用同一份资源)。

使用到的函数:

  1. 标准IO函数(fprintf)【用于打印错误信息】
  2. 文件IO函数(open、close、lseek)
  3. 有关线程的函数(pthread_create、pthread_exit、pthread_join)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <head.h>
//线程的执行体
void* callback_1(void* arg)   //void* arg = (void*)&c
{
    umask(0);
    int fp_r=open("./1.png",O_RDONLY);                                               
    if(fp_r < 0)
    {
        ERR_MSG("open");
    }
    int fp_w=open("./copy.png",O_WRONLY);
    if(fp_w <0)
    {
        ERR_MSG("open");
    }
    char c = 0;
    off_t len=lseek(fp_r,0,SEEK_END);
    int i=0;
    lseek(fp_r,0,SEEK_SET);
    lseek(fp_w,0,SEEK_SET);
    for(i=0;i<len/2;i++)
    {
        bzero(&c,sizeof(c));
        read(fp_r,&c,1);
        write(fp_w,&c,1);
    }

    close(fp_r);
    close(fp_w);

    printf("前半部分拷贝完毕\n");
    pthread_exit(NULL);
}
void* callback_2(void* arg)
{
    umask(0);
    int fp_r=open("./1.png",O_RDONLY);
    if(fp_r < 0)
    {
        ERR_MSG("open");
    }
    int fp_w=open("./copy.png",O_WRONLY);
    if(fp_w < 0)
    {
        ERR_MSG("open");
    }
    char c = 0;
    off_t len=lseek(fp_r,0,SEEK_END);
    int i=0;
    lseek(fp_r,len/2,SEEK_SET);
    lseek(fp_w,len/2,SEEK_SET);
    for(i=0;i<len/2;i++)
    {
        bzero(&c,sizeof(c));
        read(fp_r,&c,sizeof(c));
        write(fp_w,&c,sizeof(c));
    }

    close(fp_r);
    close(fp_w);
    printf("后半部分拷贝完毕\n");
    pthread_exit(NULL);

}
int main(int argc, const char *argv[])
{
    //两个线程在拷贝前,确保文件w存在,且是清空状态
    int fp_w=open("./copy.png",O_WRONLY|O_CREAT|O_TRUNC,0664);
    if(fp_w <0)
    {
        ERR_MSG("open");
    }
    close(fp_w);

    pthread_t tid_1,tid_2;
    if(pthread_create(&tid_1,NULL,callback_1,NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n",__LINE__);
        return -1;
    }

    pthread_join(tid_1,NULL);

    if(pthread_create(&tid_2,NULL,callback_2,NULL)!=0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n",__LINE__);
        return -1;
    }

    pthread_join(tid_2,NULL);
    printf("主线程准备退出... ...\n");
    return 0;
}
                                                                                     
                                                                                    

方法二:

        创建一个结构体用于存放需要打开的两个文件文件标识符、需要拷贝的字节大小。

        在主函数中打开两个文件,计算好需要拷贝的字节大小,再创建两个线程,将结构体fileinfo的地址传递到线程中(强转成(void*)类型再传,否则报错),线程中用指针void* arg接fileinfo的地址。线程中需要将指针arg的地址转为struct Msg类型。

        PS:两个线程共享同一份资源,使用pthread_exit函数使线程1先完成拷贝(与sleep达到的效果一致)。

使用到的函数:

  1. 结构体struct
  2. 标准IO函数(fprintf)【用于打印错误信息】
  3. 文件IO函数(open、close、lseek)
  4. 有关线程的函数(pthread_create、pthread_exit、pthread_join)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <head.h>
struct Msg
{
	int fp_r;
	int fp_w;
	off_t size;
};
//线程1的执行体:拷贝前半部分
void* callback_1(void* arg)   //void* arg = &fileinfo
{
	struct Msg *tp=(struct Msg*)arg;
	int fp_r=tp->fp_r;
	int fp_w=tp->fp_w;
	off_t size=tp->size;
	
	//将光标偏移到开头,拷贝size/2个字节到目标文件中
	lseek(fp_r,0,SEEK_SET);
	lseek(fp_w,0,SEEK_SET);
	char c = 0;
	for(int i=0;i<size/2;i++)
	{
		bzero(&c,sizeof(c));
		read(fp_r,&c,1);
		write(fp_w,&c,1);
	}
	printf("前半部分拷贝完毕\n");
	pthread_exit(NULL);//退出分支线程
}
//线程2的执行体
void* callback_2(void* arg)  //void* arg = &fileinfo
{
	//sleep(5);//主动放弃cpu资源,让线程1先执行
	struct Msg *tp=(struct Msg*)arg;
	int fp_r=tp->fp_r;
	int fp_w=tp->fp_w;
	off_t size=tp->size;

	//将光标偏移到size/2的位置,拷贝size/2个字节到目标文件中
	lseek(fp_r,size/2,SEEK_SET);
	lseek(fp_w,size/2,SEEK_SET);
	char c = 0;
	for(int i=0;i<size/2;i++)
	{
		bzero(&c,sizeof(c));
		read(fp_r,&c,sizeof(c));
		write(fp_w,&c,sizeof(c));
	}
	printf("后半部分拷贝完毕\n");
	pthread_exit(NULL);//退出分支线程
}
int main(int argc, const char *argv[])
{
	struct Msg fileinfo;
	//以读的方式打开1.png
	fileinfo.fp_r=open("./1.png",O_RDONLY);
	if(fileinfo.fp_r < 0)
	{
		ERR_MSG("open");
		return -1;
	}
	//以写的方式打开并创建(若存在则清空)copy.png
	fileinfo.fp_w=open("./copy.png",O_WRONLY|O_CREAT|O_TRUNC,0664);
	if(fileinfo.fp_w <0)
	{
		ERR_MSG("open");
		return -1;
	}
	//计算需要拷贝的字节大小
	fileinfo.size=lseek(fileinfo.fp_r,0,SEEK_END);

	//创建2个线程
	pthread_t tid_1,tid_2;
	if(pthread_create(&tid_1,NULL,callback_1,(void *)&fileinfo) != 0)
	{
		fprintf(stderr, "pthread_create failed __%d__\n",__LINE__);
		return -1;
	}
	pthread_join(tid_1,NULL);//阻塞等待线程1完成

	if(pthread_create(&tid_2,NULL,callback_2,(void *)&fileinfo) !=0)
	{
		fprintf(stderr, "pthread_create failed __%d__\n",__LINE__);
		return -1;
	}
	pthread_join(tid_2,NULL);//阻塞等待线程2完成

	//关闭文件
	close(fileinfo.fp_r);
	close(fileinfo.fp_w);
	printf("主线程准备退出\n");
	return 0;
}

2.5 pthread_detach

功能:分离线程,线程退出后资源由系统自动回收;

原型:

       #include <pthread.h>

       int pthread_detach(pthread_t thread);

参数:

        pthread_t thread:指定要分离哪个线程;

返回值:

        成功,返回0;

        失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;

  • 当使用pthread_detach分离tid线程后,pthread_join函数就无法再回收tid线程的资源了,且pthread_join函数不阻塞

2.6 pthread_cancel

功能:请求指定线程退出; 请求成功,对方线程不一定退出

原型:

       #include <pthread.h>

       int pthread_cancel(pthread_t thread);

参数:

        pthread_t thread:指定要请求哪个线程退出;

返回值:

        成功,返回0;

        失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;

注意:

  1. pthread_cancel会给目标线程打上一个退出标识,cpu切换到目标线程后,运行到退出标识,才会让目标线程退出。
  2. 但是while for 等循环结构,以及算数运算等等位置,无法打上退出标识。所以当目标线程只有上述结构的时候,无法用pthread_cancel退出线程
  3. printf ,sleep等等函数结构可以打上退出标识
  4. 请求成功,不代表目标线程一定会退出。

练习:要求定义一个全局变量 char buf[] = "1234567",创建两个线程,不考虑退出条件

         要求定义一个全局变量 char buf[] = "1234567",创建两个线程,不考虑退出条件,另:

  1. A线程循环打印buf字符串,
  2. B线程循环倒置buf字符串,即buf中本来存储1234567,倒置后buf中存储7654321. 不打印!!
  3. 倒置不允许使用辅助数组。
  4. 要求A线程打印出来的结果只能为 1234567 或者 7654321 不允许出现7634521 7234567
  5. 不允许使用sleep函数

方法一:使用flag将其分离

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <head.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

char buf[]="1234567";
int flag= 0 ;

void* callback_1(void* arg)//打印
{
	while(1)
	{
		if(0 == flag)
		{
			printf("%s\n",buf);
			flag=1;
		}
	}
	pthread_exit(NULL);
}

void* callback_2(void* arg)//逆置 不打印
{
	char t=0;
	while(1)
	{
		if(1 == flag)
		{
			for(int i=0;i<strlen(buf)/2;i++)
			{
				t=buf[i];
				buf[i] = buf[strlen(buf)-1-i];
				buf[strlen(buf)-1-i] = t;
			}
			flag=0;
		}
	}
	pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
	pthread_t tid_1,tid_2;
	if(pthread_create(&tid_1,NULL,callback_1,NULL)!=0)
	{
		fprintf(stderr,"pthread_create failed __%d__\n",__LINE__);
		return -1;
	}
	pthread_detach(tid_1);  //分离线程1
	if(pthread_create(&tid_2,NULL,callback_2,NULL)!=0)
	{
		fprintf(stderr,"pthread_create failed __%d__\n",__LINE__);
		return -1;
	}

	pthread_join(tid_2,NULL);
	printf("主线程准备退出");
	return 0;
}

 方法二:使用互斥锁

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <head.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
//临界资源
char buf[]="1234567";

//互斥锁
pthread_mutex_t mutex;

void* callback_1(void* arg)//打印
{
	while(1)
	{
		/***********临界区**************/
		//上锁
		pthread_mutex_lock(&mutex);

		printf("%s\n",buf);

		//解锁
		pthread_mutex_unlock(&mutex);
		/***********临界区**************/
	}
	pthread_exit(NULL);
}

void* callback_2(void* arg)//逆置 不打印
{
	char t=0;
	while(1)
	{
		/***********临界区**************/
		//上锁
		pthread_mutex_lock(&mutex);

		for(int i=0;i<strlen(buf)/2;i++)
		{
			t=buf[i];
			buf[i] = buf[strlen(buf)-1-i];
			buf[strlen(buf)-1-i] = t;
		}
		//解锁
		pthread_mutex_unlock(&mutex);
		/***********临界区**************/
	}
	pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
	//申请一个互斥锁
	pthread_mutex_init(&mutex,NULL);

	pthread_t tid_1,tid_2;
	if(pthread_create(&tid_1,NULL,callback_1,NULL)!=0)
	{
		fprintf(stderr,"pthread_create failed __%d__\n",__LINE__);
		return -1;
	}
	pthread_detach(tid_1);  //分离线程1

	if(pthread_create(&tid_2,NULL,callback_2,NULL)!=0)
	{
		fprintf(stderr,"pthread_create failed __%d__\n",__LINE__);
		return -1;
	}

	pthread_join(tid_2,NULL);  //阻塞等待线程2退出
	
	//销毁互斥锁
	pthread_mutex_destroy(&mutex);
	return 0;
}

三、线程的同步互斥(重点!!)

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

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

相关文章

数据结构—树和二叉树

5.树和二叉树 5.1树和二叉树的定义 树形结构&#xff08;非线性结构&#xff09;&#xff1a;结点之间有分支&#xff0c;具有层次关系。 5.1.1树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集。 若n0&#xff0c;称为空树&#x…

链表的总体涵盖以及无哨兵位单链表实现——【数据结构】

&#x1f60a;W…Y&#xff1a;个人主页 在学习之前看一下美丽的夕阳&#xff0c;也是很不错的。 如果觉得博主的美景不错&#xff0c;博客也不错的话&#xff0c;关注一下博主吧&#x1f495; 在上一期中&#xff0c;我们说完了顺序表&#xff0c;并且提出顺序表中的问题 1. 中…

C++ ------ 类和对象的深究

文章目录 构造函数初始化列表概念特性 explicit关键字 static成员概念特点 友元友元函数友元类概念特性 内部类概念特点 匿名对象拷贝对象时的一些编译器优化 构造函数 我们来看下面的代码&#xff1a; #include <iostream> using namespace std;class Date { public:D…

三周目创作纪念日

机缘收获日常成就憧憬 机缘 最初成为创作者的初心 实战项目中的经验分享日常学习过程中的记录通过文章进行技术交流 收获 在创作的过程中都有哪些收获 获得了很多粉丝的关注获得了很多正向的反馈&#xff0c;如赞、评论、阅读量等认识了很多志同道合的领域同行 日常 当前创…

【零基础学Rust | 基础系列 | Hello, Rust】编写并运行第一个Rust程序

文章目录 前言一&#xff0c;创建项目二&#xff0c;两种编译方式1. 使用rustc编译器编译2. 使用Cargo编译 总结 前言 在开始学习任何一门新的编程语言时&#xff0c;都会从编写一个简单的 “Hello, World!” 程序开始。在这一章节中&#xff0c;将会介绍如何在Rust中编写并运…

CSS学习记录(基础笔记)

CSS简介: CSS 指的是层叠样式表* (Cascading Style Sheets)&#xff0c;主要用于设置HTML页面的文字内容&#xff08;字体、大小、对齐方式&#xff09;&#xff0c;图片的外形&#xff08;边框&#xff09; CSS 描述了如何在屏幕、纸张或其他媒体上显示 HTML 元素 CSS 节省…

JVM面试题--实践

目录 JVM 调优的参数可以在哪里设置参数值 war包部署在tomcat中设置 jar包部署在启动参数设置 JVM 调优的参数都有哪些&#xff1f; 设置堆空间大小 虚拟机栈的设置 年轻代中Eden区和两个Survivor区的大小比例 年轻代晋升老年代阈值 设置垃圾回收收集器 JVM 调优的工…

《高质量数字化转型产品及服务全景图(2023上半年度)》希尔贝壳成功入选

2023年7月27日&#xff0c;由中国信息通信研究院泰尔终端实验室主办的2023数字生态发展大会暨中国信通院“铸基计划”年中会议在北京成功召开。在本次会上&#xff0c;中国信通院重磅发布《高质量数字化转型产品及服务全景图&#xff08;2023上半年&#xff09;》&#xff0c;希…

MySQL索引1——基本概念与索引结构(B树、R树、Hash等)

目录 索引(INDEX)基本概念 索引结构分类 BTree树索引结构 Hash索引结构 Full-Text索引 R-Tree索引 索引(INDEX)基本概念 什么是索引 索引是帮助MySQL高效获取数据的有序数据结构 为数据库表中的某些列创建索引&#xff0c;就是对数据库表中某些列的值通过不同的数据结…

MVC配置原理

如果你想保存springboot的mvc配置并且还想自己添加自己的配置就用这个。 视图解析器原理&#xff0c;它会从IOC容器里获取配置好视图解析器的配置类里的视图解析器集合&#xff0c; 然后遍历集合&#xff0c;生成一个一个的视图对象&#xff0c;放入候选 视图里&#xff0c;…

【华秋干货铺】PCB布线技巧升级:高速信号篇

如下表所示&#xff0c;接口信号能工作在8Gbps及以上速率&#xff0c;由于速率很高&#xff0c;PCB布线设计要求会更严格&#xff0c;在前几篇关于PCB布线内容的基础上&#xff0c;还需要根据本篇内容的要求来进行PCB布线设计。 高速信号布线时尽量少打孔换层&#xff0c;换层优…

word转pdf两种方式(免费+收费)

一、免费方式 优点&#xff1a;1、免费&#xff1b;2、在众多免费中挑选出的转换效果相对较好&#xff0c;并且不用像openOffice那样安装服务 缺点&#xff1a;1、对字体支持没有很好&#xff0c;需要安装字体库或者使用宋体&#xff08;对宋体支持很好&#xff09;2、对于使…

使用vuex让购物车联动

// 1.vuex点击加减触发函数提交仓库把我们请求的数据存到仓库 2.在仓库定义这个函数和对象 把我们存进去的数据存起来 // 3。在我们需要的页面拿出数据&#xff0c;然后循环就可以 // 4.当我们点击加号就触发函数然后在vuex对这个数据进行处理 // 5.对我们点进来的数据进行一个…

使用自适应去噪在线顺序极限学习机预测飞机发动机剩余使用寿命(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【零基础学Rust | 基础系列 | Rust初相识】Rust简介与环境配置

教程目录 前言一&#xff0c;Rust简介1&#xff0c;Rust的历史2&#xff0c;Rust的特性3&#xff0c;为什么选择Rust4&#xff0c;Rust可以做什么 二&#xff0c; Rust环境配置1&#xff0c;windows11安装2&#xff0c;Linux安装 三&#xff0c;安装IDE 前言 Rust是一种系统编…

无头单链表,有完整测试程序

&#x1f35f;无头单链表 &#x1f47b;无头单链表的所有结点都存储有效信息 &#x1f47b;无头单链表相对带头单链表&#xff0c;在有些涉及更改头节点的函数上需要传二级指针 &#x1f35f;头文件list.h #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #includ…

不能乱点链接之获取cookie

这里是浏览器存储的某个网址的cookie 然后点击了链接就把参数获取到 因为document.cookie 会直接获取到浏览器cookie 所以为了拦截 存cookie的时候要设置&#xff1a; 设置httpOnly 只要http协议能够读取和携带 再document.cookie 就为空了 原文链接&#xff1a; 尚硅谷课程…

后端整理(MySql)

1 事务 1.1 事务ACID原则 原子性&#xff08;Atomicity&#xff09; 事务的原子性指的是事务的操作&#xff0c;要么全部成功&#xff0c;要么全部失败回滚 一致性&#xff08;Consistency&#xff09; 事务的一致性是指事务必须使数据库从一个一致状态转变成另一个一致性…

SolidUI社区-从开源社区角度思考苹果下架多款ChatGPT应用

文章目录 背景下架背景下架原因趋势SolidUI社区的未来规划结语如果成为贡献者 背景 随着文本生成图像的语言模型兴起&#xff0c;SolidUI想帮人们快速构建可视化工具&#xff0c;可视化内容包括2D,3D,3D场景&#xff0c;从而快速构三维数据演示场景。SolidUI 是一个创新的项目…

Typescript中的元组与数组的区别

Typescript中的元组与数组的区别 元组可以应用在经纬度这样明确固定长度和类型的场景下 //元组和数组类似&#xff0c;但是类型注解时会不一样//元组赋值的类型、位置、个数需要和定义的类型、位置、个数完全一致&#xff0c;不然会报错。 // 数组 某个位置的值可以是注解中的…