11.1 Linux_线程_线程相关函数

news2024/11/29 20:45:10

概述

线程的共享资源:

可执行的指令、静态数据、文件描述符、当前工作目录、用户ID、用户组ID

线程的私有资源:

线程ID、程序计数器PC和相关寄存器、堆栈、错误号、优先级、执行状态和属性

线程编译:

gcc <.c文件> -l pthread -o <可执行文件>

线程的运行特点:

如果主线程main退出,则其他线程也被终止。因为main退出,进程的空间被释放,所有线程的空间也不复存在。

命令行查看线程状态:

ps -eLf | grep <程序名>

相关函数

1、创建线程

函数声明如下:

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

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

thread:线程对象,将创建的新线程的TID号存入该变量

attr:线程的属性,NULL代表默认的属性。

         对于默认属性,线程占有的资源不会因执行结束而释放。

start_routine:线程的回调函数

arg:线程的回调函数的参数

2、线程退出

函数声明如下:

void pthread_exit(void *retval);

retval:线程退出时的返回值,该值可被pthread_join获取。

注意:当线程调用该函数后,线程结束,但线程的资源未被回收,最终资源被pthread_join回收。即:功能与exit退出进程类似

3、获取自己的线程ID(TID)

函数声明如下:

pthread_t pthread_self(void);

返回值:自己的TID号

4、线程回收

函数声明如下:

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

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

thread:线程ID

retval:接收pthread_exit的返回值

注意:调用该函数是线程会进入阻塞状态,直到被等待的线程调用pthread_exit结束。被等待的线程结束后,pthread_join会回收退出线程的资源,并且解除阻塞

注意:pthread_join只能依次回收线程。比如先回收线程0,再回收线程1,如果这时线程0一直不结束,而线程1先结束,此时线程1不会被回收,而是等待线程0结束之后,线程1才回收。

5、线程分离

5.1 调用函数 

函数声明如下:

int pthread_detach(pthread_t thread);

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

thread:线程ID

注意:调用该函数后指定的线程与主控线程断开关系,指定的线程结束后不会产生僵尸线程,即:自动回收资源。该函数可以由主线程调用,也可以由新创建的线程调用。

5.2 创建时设置线程属性 

函数声明如下:

//初始化线程属性变量attr
int pthread_attr_init(pthread_attr_t *attr);
//设置线程属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

attr: 线程属性

detachstate:分离状态,写 PTHREAD_CREATE_DETACHED

设置完成之后,将attr传入pthread_create的第二个参数attr即可在创建时完成线程分离。

6、取消线程

取消线程的作用是让主线程能够主动的让创建的线程进行退出,也可以让自己取消自己。

函数声明如下:

//取消线程
int pthread_cancel(pthread_t thread);
//手动设置取消点
void pthread_testcancel(void);
//设置取消状态
int pthread_setcancelstate(int state, int *oldstate);
//设置取消的时刻
int pthread_setcanceltype(int type, int *oldtype);

thread:线程ID

state:线程是否可以被取消

            允许被取消:PTHREAD_CANCEL_ENABLE(默认)

            禁止被取消:PTHREAD_CANCEL_DISABLE

type:线程取消时刻的模式

           等到取消点才取消:PTHREAD_CANCEL_DEFERRED(默认)

           目标线程会立刻取消:PTHREAD_CANCEL_ASYNCHRONOUS

oldstate:传入NULL

注意:线程的取消要有取消点,取消点就是阻塞的系统调用。当没有取消点时,该函数不会进入阻塞,并且对指定的线程也没有任何影响。printf缓冲区满时,也会算作取消点。

注意:调用pthread_cancel,指定的线程被取消,这意味着该线程在调用pthread_exit之前进行了退出。除此之外,pthread_cancel会自动回收线程的资源。

pthread_testcancel的使用:

该函数手动设置取消点,并不是说执行到该函数才设置,而是线程中存在这个函数,不论能否执行到,都可以进行取消。???????

pthread_setcancelstate的使用:

pthread_setcancelstate用于保护线程中的部分代码段完整执行,在这期间线程不可被取消。

使用方法如下:

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);

//线程中不可被取消的代码段

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

示例见示例代码:设置线程是否可以被取消

注意:在创建完线程之后,如果直接取消该线程,那么该线程中尽管在刚开始时就禁止取消,也有可能被取消。因为创建线程需要时间,当主线程取消线程时,新的线程还没有执行禁止取消的函数。

7、线程清理

线程清理的作用是释放线程取消之前申请的内存空间,这个释放代码需要自己编写。

函数声明如下:

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

void pthread_cleanup_pop(int execute);

routine:清理函数

arg:清理函数的参数

execute:写0代表不执行routine,非0代表执行routine

注意:这两个函数必须成对使用,并且pthread_cleanup_push后不加分号

注意:这两个函数可以多次使用,效果类似栈,先push的后pop

注意:这两个并不是函数,而是宏定义

routine被执行的情况:

  • 当主线程调用pthread_cancel取消了线程
  • 该线程调用了pthread_exit退出了线程
  • 执行到pthread_cleanup_pop,并且传参为非0

注意:当调用return退出线程时,routine不执行

示例代码

1、创建线程

具体代码实现如下:

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

//线程0回调函数
void* pthread_0(void* arg){
	while(1){
		printf("this is pthread 0\n");
		sleep(1);
	}
	//退出线程
	pthread_exit(NULL);
}

//线程1回调函数
void* pthread_1(void* arg){
	while(1){
		printf("this is pthread 1\n");
		sleep(1);
	}
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid[2];//多个线程时,tid用数组存储

	//创建线程
	if(pthread_create(&tid[0],NULL,(void*)pthread_0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	if(pthread_create(&tid[1],NULL,(void*)pthread_1,NULL) != 0){
		perror("pthread_create");
		return -1;
	}

	while(1){
		printf("this is main\n");
		sleep(1);
	}
	return 0;
}

代码运行结果如下:

2、获取线程TID

具体代码实现如下:

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

//线程回调函数
void* pthread_0(void* arg){
	//线程中查看自己的TID
	printf("pthread:tid0 = %ld\n",pthread_self());
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid;
	//创建线程
	if(pthread_create(&tid,NULL,(void*)pthread_0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	//main中查看所创建线程的TID
	printf("main:tid0 = %ld\n",tid);
	while(1);
	return 0;
}

代码运行结果如下:

3、参数传递

3.1 传递一个参数

传递一个参数,只需要将这个参数的地址进行传递即可。

具体代码实现如下:

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

//线程回调函数
void* pthread_0(void* arg){
	//获取传入的参数
	printf("arg = %d\n",*((int*)arg));
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid;
	int arg0 = 100;
	//创建线程
	if(pthread_create(&tid,NULL,(void*)pthread_0,&arg0) != 0){
		perror("pthread_create");
		return -1;
	}
	while(1);
	return 0;
}

代码运行结果如下:

3.2 传递多个参数

传递多个参数,就是将多个参数捆为一个结构体,传入的是结构体的指针。

具体代码实现如下:

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

//线程回调函数
struct test{
	int num;
	char a;
};
void* pthread_0(void* arg){
	//获取传入的参数
	printf("arg.num = %d\n",((struct test*)arg)->num);
	printf("arg.a = %c\n",((struct test*)arg)->a);
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid;
	struct test arg0 = {300,'a'};

	//创建线程
	if(pthread_create(&tid,NULL,(void*)pthread_0,&arg0) != 0){
		perror("pthread_create");
		return -1;
	}
	while(1);
	return 0;
}

代码运行结果如下:

3.3 传递的参数经常变化

在3.1、3.2中,传递的参数都是固定的,因此传递指针是可以的。但如果参数会在main中发送改变,那么线程在接收到指针后,访问内存的数据不一定是传入时的数据,即:访问时main已经把数据进行了修改。

针对这个问题,可以将数据直接强制转为void*,进行传递。

具体代码实现如下:

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

//线程0回调函数
void* pthread_0(void* arg){
	printf("num:%d,tid = %ld\n",arg,pthread_self());
	//退出线程
	pthread_exit(NULL);
}

int main(){

	pthread_t tid[10];//多个线程时,tid用数组存储
	int i;
	for(i=0;i<10;i++){
		//创建线程
		if(pthread_create(&tid[0],NULL,(void*)pthread_0,(void*)i) != 0){//将i强转为void*
			perror("pthread_create");
			return -1;
		}
	}
	while(1){
		printf("this is main\n");
		sleep(1);
	}
	return 0;
}

代码运行结果如下:

4、等待线程退出

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	int i;
	for(i=0;i<3;i++){
		printf("this is pthread0\n");
		sleep(1);
	}
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	//等待线程退出,main进入阻塞
	pthread_join(tid,&ret0);
	printf("ret0:%s\n",(char*)ret0);//打印线程0退出状态
	while(1);

	return 0;
}

代码运行结果如下:

5、线程分离

5.1 调用pthread_detach

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	
	//pthread_detach(pthread_self());//也可以在这里进行线程分离
	printf("this is pthread0\n");
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	pthread_t tid1;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		pthread_detach(tid);//线程分离
		return -1;
	}
	while(1);

	return 0;
}

5.2 设置线程属性为分离

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	printf("this is pthread0\n");
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	pthread_attr_t attr;
	//初始化线程属性
	pthread_attr_init(&attr);
	//设置线程属性为分离
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	//创建线程,创建时传入attr
	if(pthread_create(&tid,&attr,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	while(1);

	return 0;
}

6、取消线程

6.1 基本使用

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	while(1){
		sleep(1);//进入阻塞,这里就是一个取消点
        //pthread_testcancel();//也可以将阻塞换为该函数,手动设置一个取消点
	}
	//线程退出
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	//取消线程
	pthread_cancel(tid);//取消之后pthread_exit没有运行
	pthread_join(tid,&ret0);//这里的ret0不会收到pthread_exit的返回值
	printf("get\n");
	while(1);

	return 0;
}

代码运行结果如下:

6.2 设置线程是否可以被取消 

具体代码实现如下:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
	int i;
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//线程禁止被取消
	for(i=0;i<3;i++){
		printf("now cannot cancel\n");
		sleep(1);
	}
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//线程可以被取消
	while(1){
		printf("now can cancel\n");
		sleep(1);
	}
	pthread_exit("pthread0 exit");
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);//创建线程后不能立刻取消,需要一些时间让线程执行程序
	pthread_cancel(tid);//取消线程
	printf("get\n");
	pthread_join(tid,&ret0);
	printf("get2\n");
	while(1);

	return 0;
}

代码运行结果如下:

7、线程清理

7.1  routine执行情况1

当主线程调用pthread_cancel取消了线程

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

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"2") //这里不能加分号
	while(1){
		sleep(1);//这里阻塞当作一个取消点	
	}
	printf("after while\n");	
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_cancel(tid);//取消线程
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

7.2  routine执行情况2

该线程调用了pthread_exit退出了线程

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

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"1") //这里不能加分号
	
	pthread_exit(NULL);//线程退出
	printf("after exit\n");
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

7.3  routine执行情况3

执行到pthread_cleanup_pop,并且传参为非0

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

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"1") //这里不能加分号
	
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

7.4 多个清理函数

多次push后,先push的后pop。

具体代码实现如下:

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

void clean(void* arg){
	//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
	printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
	
	pthread_cleanup_push(clean,"1") //这里不能加分号
	pthread_cleanup_push(clean,"2") //这里不能加分号
	pthread_cleanup_push(clean,"3") //这里不能加分号
	
	pthread_cleanup_pop(1);
	printf("after pop\n");
	while(1){
		printf("now in while\n");
		sleep(1);//阻塞作为取消点 
	}
	printf("after while\n");
	pthread_exit(NULL);//线程退出
	printf("after exit\n");
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
}
int main(){

	pthread_t tid;
	void* ret0 = NULL;
	//创建线程
	if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_cancel(tid);
	pthread_join(tid,&ret0);
	printf("get\n");
	while(1);

	return 0;
}

代码运行结果如下:

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

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

相关文章

代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作删除二叉搜索树中的节点修剪二叉搜索树

代码随想录 | Day26 | 二叉树&#xff1a;二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树 主要学习内容&#xff1a; 二叉搜索树的插入删除操作 701.二叉搜索树中的插入操作 701. 二叉搜索树中的插入操作 - 力扣&#xff08;LeetCode&…

企业级数据备份一般都是怎么做的?来唠唠嗑

小白最近去了很多企业看了一下他们的存储方案&#xff0c;基本上都是单硬盘数据存储&#xff0c;一个硬盘10TB&#xff08;实际可用8TB左右&#xff09;。 这些大概是大部分微小企业存储数据的办法&#xff0c;也是他们能想到的最好办法了吧。 截至2024年的今天&#xff0c;咱…

MATLAB|基于多主体主从博弈的区域综合能源系统低碳经济优化调度

目录 主要内容 程序亮点&#xff1a; 模型研究 一、综合能源模型 二、主从博弈框架 部分代码 结果一览 下载链接 主要内容 程序参考文献《基于多主体主从博弈的区域综合能源系统低碳经济优化调度》&#xff0c;采用了区域综合能源系统多主体博弈协同优化方…

vim 操作

vim编辑器的有三种工作模式&#xff1a;命令模式、插入模式和底行命令模式 打开进入命令模式&#xff1a; 由命令模式到输入模式&#xff1a;i:在光标前插&#xff1b;a:在光标后插&#xff1b;o:在下一行插 由输入模式进入命令模式&#xff1a;esc 由命令模式进入底行命令…

判断推理(2)

集合推理: 这是不能串在一起的&#xff0c;再进行合并推理的时候有的一定要放在开头 D D 第二句属于真假推理 然后进行翻译推理的时候一定要让有的打头&#xff0c;所以让1或者是2打头&#xff0c;但是1如果是最开头的话那么就什么也推不出来&#xff0c;所以只能是2打头B B A…

Pikachu-Cross-Site Scripting-xss之htmlspecialchars

首先输入各种字符 查看页面元素&#xff0c;可以看到这里对一些符号做了转换&#xff0c;但是 单引号等几个符号没处理&#xff1b; 从代码上看&#xff1b;使用单引号做闭合&#xff1b; 构造payload a onclickalert(11) 提交&#xff0c;得到xss攻击

【Java】springboot 项目中出现中文乱码

在刚创建的springboot项目中&#xff0c;出现乱码&#xff0c;跟走着解决一下 1、Ctrl Shift S 打开idea设置&#xff0c;根据图片来&#xff0c;将③④这三个地方都修改为UTF-8 2、返回配置查看&#xff0c;解决

僵尸进程、孤儿进程和守护进程

让我们详细讨论僵尸进程、孤儿进程和守护进程。 1. 僵尸进程 (Zombie Process) 定义: 僵尸进程是指一个已经终止执行&#xff08;结束运行&#xff09;&#xff0c;但其父进程尚未对其进行清理&#xff08;调用wait()或waitpid()系统调用来获取子进程的退出状态&#xff09;的…

Docker 从安装到实战

Docker 是一个开源的平台&#xff0c;用于自动化应用程序的部署、扩展和管理。它利用操作系统级别的虚拟化&#xff0c;将应用程序及其依赖项封装在称为容器的轻量级、可移植的单元中。以下是 Docker 的一些关键特点&#xff1a; 容器化&#xff1a;Docker 容器可以在任何支持 …

(8)MATLAB瑞利衰落信道仿真1

文章目录 一、瑞利随机变量及其概率密度函数二、仿真代码三、仿真结果四、仿真代码的几点补充说明其他 一、瑞利随机变量及其概率密度函数 在无线通信中&#xff0c;如果信道中存在大量非视距路径而不存在视距路径信号分量&#xff0c;此时&#xff0c;无线信道可以由一个复高…

深入浅出,从源码搞清Bean的加载过程

深入浅出&#xff0c;从源码搞清Bean的加载过程 前言 Bean的加载过程算是面试中的老生常谈了&#xff0c;今天我们就来从源码层面深入去了解一下Spring中是如何进行Bean的加载的 Spring 先看示例代码&#xff1a; public static void main(String[] args) {ApplicationCon…

微服务之间的相互调用的几种常见实现方式对比

目录 微服务之间的相互调用的几种实现方式 一、HTTP HTTP/RESTful API调用工作原理 二、RPC 设计理念与实现方式 协议与传输层 RPC远程调用工作原理 应用场景与性能考量 特点 三、Feign 设计理念与实现方式 协议与传输层 Feign调用的基本流程 Feign调用的工作原理…

算法训练营打卡Day19

目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有…

温度转换-C语言

1.问题&#xff1a; 输入一个华氏温度&#xff0c;要求输出摄氏温度。公式为 c5(F-32)/9&#xff0c;取位2小数。 2.解答&#xff1a; scanf("%lf",&f);或者scanf("%f",&f);如果你前面定义的f是用double类型的话&#xff0c;就应该用%lf格式&…

deploy thingsboard

ThingsBoard部署 平台&#xff1a;windows10&#xff0c;idea2022&#xff0c;postgres15 maven仓库 进入thingsboard源码下载目录: 主要执行以下两个命令&#xff1a; mvn编译&#xff1a; mvn clean install -Dmaven.test.skiptrue编译报错时&#xff1a; 清除java进程 t…

计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

UART通信—基于江科大源码基础进行的改进和解析

我就不讲理论了&#xff0c;CSDN上大佬属实多&#xff0c;我就只讲代码了&#xff0c;串口的基本理论&#xff0c;大家去看其他大佬写的吧 一、源文件的组成 1、包含的头文件 stm32f10x.h 是STM32F10x系列微控制器的标准外设库&#xff08;Standard Peripheral Library&…

C语言基础(7)之操作符(1)(详解)

目录 1. 各种操作符介绍 1.1 操作符汇总表 2. 移位操作符 2.1 移位操作符知识拓展 —— 原码、反码、补码 2.2 移位操作符讲解 2.2.1 右移操作符 ( >> ) 2.2.2 左移操作符 ( << ) 3. 位操作符 3.1 & (按位与) 3.2 | (按位或) 3.3 ^ (按位异或) 3.4…

【AI学习】Mamba学习(二):线性注意力

上一篇《Mamba学习&#xff08;一&#xff09;&#xff1a;总体架构》提到&#xff0c;Transformer 模型的主要缺点是&#xff1a;自注意力机制的计算量会随着上下文长度的增加呈平方级增长。所以&#xff0c;许多次二次时间架构&#xff08;指一个函数或算法的增长速度小于二次…