Lab 4线程的创建与管理

代码在页底

一.“hello world"单线程

#include<stdio.h>
int main(void){p_msg("hello ");p_msg("world\n"); return 0;
}void p_msg(char *s){int  i;for(i=0;i<5;i++){printf("%s",s);fflush(stdout);sleep(1);}
}

1)此例程使用函数调用,理解函数调用是顺序执行的;

1.表现形式:先打印 hello,再打印world

2)理解fflush的作用

fflush():
1.单词释义:flush:冲洗  fflush:刷新缓冲区
2.作用:清洗读写缓冲区,立即物理写入输出缓冲区的数据
3.类型:fflush(stdin):刷新输入缓冲区,将输入缓冲区的数据丢弃fflush(stdout):刷新输出缓冲区,将输出缓冲区数据打印到输出设备   4.fflush(out)在源程序中作用:sleep(1)使得进程休眠,但是输出的字符串已经保留到缓冲区fflush(out)表示,尽管在休眠状况下,直接将缓冲区的数据打印到输出设备(显示屏)
5.实例:去掉fflush(out),实验结果:等五秒后电脑一次打印完”hello hello hello hello hello world“但是改写printf("hello")->printf("hello\n"),去掉fflush(out)仍就是每秒打印一次"hello"


二."hello world"多线程

易错提醒:
1.代码:pthread_create(&t1,NULL,p_msg,(void*)“hello”);
–>pthread_create(&t1,NULL,(void*)p_msg,(void*)“hello”);
2.编译: gcc -o t t.c -lpthread
-lpthread 链接线程库

#include<stdio.h>
#include<pthread.h>
void *p_msg(char *m){char *cp=(char*) m;int i;for(i=0;i<5;i++){printf("%s",m);fflush(stdout);sleep(1); }return NULL;
}
int main(void){pthread_t t1,t2;void *p_msg(char*);pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");   //注意:p_msg->(void *)p_msgpthread_create(&t2,NULL,(void*)p_msg,(void*)"world\n");    //pthread_join(t1,NULL);pthread_join(t2,NULL);return 0;
}

1)从输出内容,观察线程的并发执行

1.  源程序先书写调用“hello"的函数,再书写调用"world"函数但图中两次不同的输出显示,hello与world输出先后顺序不定,从而表示了两个线程是并发执行的
2.  并发:一定时间段内,多个程序交替使用CPU,但在任意时间点上只有一个程序在CPU上运行


2)运行例程,用ps -aL ,ps -efl,pstree -p观察线程信息

1.ps -aL

ps -aL
1.命令提示符:ps -a:选择除两个会话头之外的所有进程以及与终端无关的进程ps -L:显示线程ps -aL:显示除两个会话头之外的所有进程以及与终端无关的进程的线程
注意:注意大小写,不能用ps -alps -l表示长格式,不会显示线程相关信息(LWP值)2.线程相关信息:PID: process identification        进程标识符LWP: light weight process      轻量级进程(线程)TTY: teletypes               虚拟控制台,串口以及伪终端设备组成的终端设备CMD: command prompt            命令提示符


2.ps -efL

ps -efL
1.命令提示符:ps -e:  显示所有进程           与ps -A一致ps -f:  做清单列表ps -L:  显示线程ps -efL:显示所有进程与线程的清单列表2.线程相关信息:UID: User ID                      用户IDPPID:Parent Process ID               父进程IDNLWP: The number of LWP                 一个进程中线程的数量STIME Start Time                  进程开始的时间


3.pstree -p

pstree -p
1.命令提示符:pstree -p: 显示进程树,在进程后括号内十进制表示PID


4.线程信息

线程信息:
1.命令提示符:gcc -lpthread:  链接线程函数库grep p1:      检索与p1相同的字符串的内容  ./p1 > p1.txt:   将p1执行的结果保存在p1.txt文件中./p1 |ps -L:    表示只统计执行p1时,相关的线程信息,"|"表示同时


5.遇到的小小问题:

gcc编译书上代码时会有提示出错,但仍可生成可执行文件?

解决方式:主函数:pthread_create(&t1,NULL,p_msg,(void*)"hello");->pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");


3)理解pthread_create()函数的作用

1.头文件:#include<pthread.h>
2.函数声明:int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void*),void *arg)
3.参数说明:pthread_t *thread:                指向线程的标识符,返回线程的IDpthread_attr_t *attr:            设置线程的属性,NULL表示默认属性void *(*start_routine)(void*): 线程运行函数的起始地址void *arg:                       传给线程启动函数的参数
4.返回值:成功创建返回0,失败返回错误码
5.pthread_create()函数说明:是类UNIX操作系统(Unix,Linux,Mac OSX等)创建线程的函数创建线程,本质是确定调用该线程函数的入口点线程创建后,就开始运行相关线程函数

4)理解pthread_join()函数的作用;删除pthread_join函数后,观察例程运行

1.头文件:#include<pthread.h>
2.函数声明:int pthread_join(pthread_t thread,void **retval)
3.参数说明:pthread_t thread:     线程标识符,即线程ID                     thread:线     pthread:线程 void **retval:           用户定义的指针,用来储存被等待线程的返回值  retval:return value
4.返回值:成功等待返回0,失败返回错误值
5.pthread_join()函数说明:A线程调用B线程并执行pthread_join()后,A线程处于阻塞状态当B线程结束,pthread_join()函数返回,回收B线程的资源,A线程才继续执行

1.结果:1.不使用pthread_join()函数,主线程可以输出,但子线程不会有输出2.仅有一个pthread_join()函数,也会有一样的输出

2.删除一个pthread_join:

3.删除两个pthread_join:

4.相关链接:

详情链接


5)改写代码,用并发的父子进程输出同样的内容,体会进程和线程的差别

#include <stdio.h>
#include <stdlib.h>int main(void)  {int i;int pid[5];for(i=0;i<5;i++)if((pid[i]=fork())==0){printf("hello");exit(0);   }   else{wait(0);printf("world\n");}return 0;
}
//注意:及时对子进程执行退出,防止其运行后面的进程
//注意:printf("world\n")中"\n"不能去掉,否则打印的次数不详

0 进程 线程
1.创建 fork() pthread_create()
2.等待终止 wait() pthread_join()
3.包括 程序,数据,TCB 程序,数据,堆栈,PCB
4. 调度,执行 调度,执行;拥有资源所有权



三.线程的并发执行和ID状态

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);int main(void){int i,ret;pthread_t a_pthread;int thread[num];for(i=0;i<num;i++)thread[i]=i;for(i=0;i<num;i++){ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));if(ret==0)printf("Pthread launch successfully!\n");}i=getchar();return 0;
}
void *PrintThread(void *n){int i;for(i=0;i<3;i++){printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());sleep(1);   }return NULL;
}

1)分析输出内容不连续的原因

由图可知,pid均为10537,lwp有所不同,表明运行的所有线程均在同一进程下
多线程共用同一进程的资源
各线程在调用PrintThread()时每次输出后sleep(1)休眠1s,该线程进入阻塞态
在其休眠期间,其他线程由就绪态->运行态,执行程序并输出


2)删除sleep(1),观察变化;分析原因

现象:每个线程内容连续输出
原因:每个线程在创建后进入就绪态,按照系统调用依次执行各线程,无sleep()和pthread_join()使用,线程正常执行,不会中断,直到线程正常结束,由运行态->死亡态 待一个线程结束后,按系统调度将就绪态中一个线程执行,变为运行态


3)分析输出内容,理解线程各种ID的含义

1.相关信息:num : 创建的第几个线程pid : 进程IDlwp : 线程           (light weight process:轻量级进程(线程))tid : 线程ID      (thread ID)(与lwp同表示线程,值不同)
2.内容分析:1.3个线程均成功创建   Pthread launch successfully!且仅有10538,10539,10540三个线程号2.共用一个进程      pid均为10537



四.POSIX互斥锁的使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<sys/types.h>int gnum=0;
pthread_mutex_t mutex;
static void pthread_add2(void);
static void pthread_add3(void);int main(void){pthread_t p1=0;pthread_t p2=0;int ret=0;printf("main program is start,gnum is %d,pid is %d\n",gnum,getpid());pthread_mutex_init(&mutex,NULL);ret=pthread_create(&p1,NULL,(void*)pthread_add2,NULL);//ret=ret=pthread_create(&p2,NULL,(void*)pthread_add3,NULL);pthread_join(p1,NULL);pthread_join(p2,NULL);printf("main program is exited\n");return 0;
}
static void pthread_add2(void){int i;printf("This is pthread1,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));for(i=0;i<3;i++){
//      pthread_mutex_lock(&mutex);gnum++;sleep(1);gnum++;printf("pthread_add 2 gnum is %d \n",gnum);
//      pthread_mutex_unlock(&mutex);   }pthread_exit(NULL);            //**
}
static void pthread_add3(void){     //**int i;printf("This is pthread2,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));for(i=0;i<4;i++){
//      pthread_mutex_lock(&mutex);gnum++;sleep(1);gnum++;sleep(1);gnum++;printf("pthread_add 3 gnum is %d \n",gnum);       //gum
//      pthread_mutex_unlock(&mutex);                   //unlock}pthread_exit(0);
}

1)保留注释,运行程序,输出混乱,分析原因

1.  由图可知,进程ID为12615的进程创建了两个线程:12616,12617.两个线程并发执行调用进程的资源当一个线程调用pthread_add2并执行sleep(1)休眠时,该线程进入阻塞期另一个线程由就绪态,被调用执行成运行态,执行pthread_add3并输出依次两个线程不断运行->阻塞,就绪->运行交替,所以输出内容交替


2)删除sleep()函数,运行程序,混乱消失,思考是否从根本上解决问题

1.   没有图1所示,同一进程产生的两个线程由系统调度,依次顺序执行全部程序和输出,并结束但是,如图2所示,由于线程是并发执行的,线程的调度由操作系统执行,所以同一优先级线程调度非顺序,导致最后输出结果gnum不一致

3)恢复sleep()函数,删掉注释,使用互斥锁,观察结果,理解互斥锁的作用

结果:1.优先输出两行"This is pthread*,pid is *,tid is *,lwp id is *"2.完整的执行完一个线程后,再执行另一个线程
互斥锁的作用:1.使得同一优先级线程可以按照顺序执行各线程2.当一个线程调用互斥锁时,其他请求锁的线程会进入等待队列,待锁释放后,按照优先级获得锁
疑问:1.为啥优先输出两行"This is pthread*,pid is *,tid is *,lwp id is *"?


4)掌握互斥锁的基本操作:初始化,加锁(P操作),解锁(V操作)

1.全局变量:       pthread_mutex_t mutex
2.动态互斥锁初始化:   pthread_mutex_init(pthread_mutex_t *restrict mutex,pthread_mutexattr_t *restrict attr)
3.加锁:              pthread_mutex_lock(pthread_mutex_t *mutex)
4.解锁:              pthread_mutex_unlock(pthread_mutex_t *mutex)

5)输出内容不在混乱,分析互斥锁与删除sleep()不同的原因

1.相同:不用sleep()和用互斥锁都可以使得输出内容有序
2.不同:不用sleep():      线程并发的一次执行完成.A线程,B线程输出先后顺序不一定用互斥锁:        线程A先创建,先开启互斥锁,线程B创建时申请锁的使用进入等待队列只有线程A释放锁后,线程B才能开始执行.A.B线程输出顺序一定

6)使用互斥锁修改例程3,使得输出结果不交错

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
pthread_mutex_t mutex;int main(void){int i,ret;pthread_t a_pthread;int thread[num];pthread_mutex_init(&mutex,NULL);for(i=0;i<num;i++)thread[i]=i;for(i=0;i<num;i++){ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));if(ret==0)printf("Pthread launch successfully!\n");}i=getchar();return 0;
}
void *PrintThread(void *n){int i;for(i=0;i<3;i++){pthread_mutex_lock(&mutex);printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());sleep(1);    pthread_mutex_unlock(&mutex);}return NULL;
}


五.POSIX互斥锁

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>int myglobal=10;
pthread_mutex_t mutex;
void *thread_function(void *arg){int i,j;       //i,j不能混用for(i=0;i<20;i++){
//      pthread_mutex_lock(&mutex);j=myglobal;j++;printf(".");fflush(stdout);usleep(1000);myglobal=j;
//      pthread_mutex_unlock(&mutex);}return NULL;
}
int main(void){int i;pthread_t mythread=0;pthread_mutex_init(&mutex,NULL);if(pthread_create(&mythread,NULL,thread_function,NULL)){printf("error creating mythread\n");abort();       //**abort(0)}for(i=0;i<20;i++){
//      pthread_mutex_lock(&mutex);myglobal++;printf("O");fflush(stdout);usleep(1000);
//      pthread_mutex_unlock(&mutex);}if(pthread_join(mythread,NULL)){printf("error joining mythread\n");abort();}printf("\n myglobal equald is %d\n",myglobal);        //**pthread_mutex_destroy(&mutex);exit(0);
}

1)观察程序加锁前后的变化,理解互斥锁的使用

1.abort()释义:  abort:终止头文件:#include<stdlib.h>作用:   异常终止当前进程
2.互斥锁的使用:效果:使得线程依次顺序执行,避免并发导致的的输出混乱,单一输出的不完整
3.疑问:1.为啥先输出‘0’,再输出'.'?(为啥主函数的线程先拿到了锁)




六.代码区

1.“hello world"单线程

#include<stdio.h>
int main(void){p_msg("hello ");p_msg("world\n"); return 0;
}void p_msg(char *s){int  i;for(i=0;i<5;i++){printf("%s",s);fflush(stdout);sleep(1);}
}

2."hello world"多线程

#include<stdio.h>
#include<pthread.h>
void *p_msg(char *m){char *cp=(char*) m;int i;for(i=0;i<5;i++){printf("%s",m);fflush(stdout);sleep(1); }return NULL;
}
int main(void){pthread_t t1,t2;void *p_msg(char*);pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");   //注意:p_msg->(void *)p_msgpthread_create(&t2,NULL,(void*)p_msg,(void*)"world\n");    //pthread_join(t1,NULL);pthread_join(t2,NULL);return 0;
}

2-1.用并发的父子进程输出同样的内容

#include <stdio.h>
#include <stdlib.h>int main(void)  {int i;int pid[5];for(i=0;i<5;i++)if((pid[i]=fork())==0){printf("hello");exit(0);   }   else{wait(0);printf("world\n");}return 0;
}
//注意:及时对子进程执行退出,防止其运行后面的进程
//注意:printf("world\n")中"\n"不能去掉,否则打印的次数不详

3.线程的并发执行和ID状态

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);int main(void){int i,ret;pthread_t a_pthread;int thread[num];for(i=0;i<num;i++)thread[i]=i;for(i=0;i<num;i++){ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));if(ret==0)printf("Pthread launch successfully!\n");}i=getchar();return 0;
}
void *PrintThread(void *n){int i;for(i=0;i<3;i++){printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());sleep(1);   }return NULL;
}

4.POSIX互斥锁的使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<sys/types.h>int gnum=0;
pthread_mutex_t mutex;
static void pthread_add2(void);
static void pthread_add3(void);int main(void){pthread_t p1=0;pthread_t p2=0;int ret=0;printf("main program is start,gnum is %d,pid is %d\n",gnum,getpid());pthread_mutex_init(&mutex,NULL);ret=pthread_create(&p1,NULL,(void*)pthread_add2,NULL);//ret=ret=pthread_create(&p2,NULL,(void*)pthread_add3,NULL);pthread_join(p1,NULL);pthread_join(p2,NULL);printf("main program is exited\n");return 0;
}
static void pthread_add2(void){int i;printf("This is pthread1,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));for(i=0;i<3;i++){
//      pthread_mutex_lock(&mutex);gnum++;sleep(1);gnum++;printf("pthread_add 2 gnum is %d \n",gnum);
//      pthread_mutex_unlock(&mutex);   }pthread_exit(NULL);            //**
}
static void pthread_add3(void){     //**int i;printf("This is pthread2,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));for(i=0;i<4;i++){
//      pthread_mutex_lock(&mutex);gnum++;sleep(1);gnum++;sleep(1);gnum++;printf("pthread_add 3 gnum is %d \n",gnum);       //gum
//      pthread_mutex_unlock(&mutex);                   //unlock}pthread_exit(0);
}

4-1.使用互斥锁修改例程3,使得输出结果不交错

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
pthread_mutex_t mutex;int main(void){int i,ret;pthread_t a_pthread;int thread[num];pthread_mutex_init(&mutex,NULL);for(i=0;i<num;i++)thread[i]=i;for(i=0;i<num;i++){ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));if(ret==0)printf("Pthread launch successfully!\n");}i=getchar();return 0;
}
void *PrintThread(void *n){int i;for(i=0;i<3;i++){pthread_mutex_lock(&mutex);printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());sleep(1);    pthread_mutex_unlock(&mutex);}return NULL;
}

5.POSIX互斥锁

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>int myglobal=10;
pthread_mutex_t mutex;
void *thread_function(void *arg){int i,j;       //i,j不能混用for(i=0;i<20;i++){
//      pthread_mutex_lock(&mutex);j=myglobal;j++;printf(".");fflush(stdout);usleep(1000);myglobal=j;
//      pthread_mutex_unlock(&mutex);}return NULL;
}
int main(void){int i;pthread_t mythread=0;pthread_mutex_init(&mutex,NULL);if(pthread_create(&mythread,NULL,thread_function,NULL)){printf("error creating mythread\n");abort();       //**abort(0)}for(i=0;i<20;i++){
//      pthread_mutex_lock(&mutex);myglobal++;printf("O");fflush(stdout);usleep(1000);
//      pthread_mutex_unlock(&mutex);}if(pthread_join(mythread,NULL)){printf("error joining mythread\n");abort();}printf("\n myglobal equald is %d\n",myglobal);        //**pthread_mutex_destroy(&mutex);exit(0);
}

ZUCC_操作系统_Lab4线程的创建与管理相关推荐

  1. Java:使用Executors创建和管理线程

    http://zhangjunhd.blog.51cto.com/113473/70068/ 1. 类 Executors 此类中提供的一些方法有: 1.1 public static Executo ...

  2. C++多线程并发(一)--- 线程创建与管理

    文章目录 前言 一.何为并发 1.1 并发与并行 1.2 硬件并发与任务切换 1.3 多线程并发与多进程并发 二.如何使用并发 2.1 为什么使用并发 2.2 在C++中使用并发和多线程 三.C++线 ...

  3. java 线程的创建和执行_线程管理(一)线程的创建和运行

    声明:本文是< Java 7 Concurrency Cookbook>的第一章, 作者: Javier Fernández González 译者:郑玉婷 校对:欧振聪 线程的创建和运行 ...

  4. 线程管理(七)守护线程的创建和运行

    声明:本文是< Java 7 Concurrency Cookbook >的第一章, 作者: Javier Fernández González 译者:郑玉婷 校对:方腾飞 守护线程的创建 ...

  5. Android官方开发文档Training系列课程中文版:线程执行操作之创建多线程管理器

    原文地址:http://android.xsoftlab.net/training/multiple-threads/create-threadpool.html 上节课我们学习了如何定义一个任务.如 ...

  6. C11头文件threads.h声明了创建和管理线程,信号,条件变量的函数

    作者Danny Kalev 是通过以色列系统分析师协会认证的系统分析师, 并且是专攻C++的软件工程师. Kalev 写了多本C++的书籍,同时给不同的软件开发者站点投搞C++文章. 他是C++标准委 ...

  7. 操作系统实验一:线程的创建与撤销

    实验一:线程的创建与撤销 2.1.1 实验目的 (1)熟悉Windows系统提供的线程创建与撤销系统调用. (2)掌握Windows系统环境下线程的创建与撤销方法. 2.1.2 实验准备知识 1.线程 ...

  8. 操作系统4小时速成:进程管理占考试40%,进程状态,组织,通信,线程拥有调度,进程拥有资源,进程和线程的区别

    操作系统4小时速成:进程管理占考试40%,进程状态,组织,通信,线程拥有调度,进程拥有资源,进程和线程的区别 2022找工作是学历.能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去 ...

  9. 操作系统实验一、线程的创建与撤销

    实验一:线程的创建与撤销 一.实验目的 (1)熟悉windows系统提供的线程创建与撤销系统调用. (2)掌握windows系统环境下线程的创建与撤销方法. 二.实验准备 线程的概念 (1)线程(th ...

最新文章

  1. OpenCV+python:像素运算
  2. 欧盟发布《人工智能道德准则》:「可信赖 AI」才是 AI 的指路明灯
  3. 赤兔四足机器人的作用_跑得快,打不死!清华大学开发“小强”机器人,壮汉狂踩也挡不住前进步伐...
  4. PAT甲级1155 Heap Paths (30 分):[C++题解]堆、堆的遍历、树的遍历、dfs输出路径、完全二叉树建树
  5. python对excel数据更改_利用python对excel中一列的时间数据更改格式代码示例
  6. java 简单阻塞队列,制作一个简单的任务队列(使用阻塞队列)
  7. Sql Server的艺术(二) SQL复杂条件搜索
  8. iOS越狱开发theOS搭建
  9. TRF7970A 天线
  10. errors collectiions
  11. 3D材质管理软件Adobe Substance 3D Sampler中文版
  12. ubuntu 设置静态路由_ubuntu 配置静态路由
  13. 数字孪生城市可视化运营管理系统 智慧城市解决方案
  14. LeetCode 105. 从前序与中序遍历序列构造二叉树(dfsdfs、边界判定情况、做一题送一题)
  15. 【冰爪游戏】MC教程 —— 自定义皮肤
  16. 使用POI 删除批注
  17. 快速制作一个chrome插件
  18. 润物无声因挚爱,育人无痕待花开
  19. nginx实现路由转发
  20. 记一次IIS发布网站导致系统时常跳入登录页面的问题解决

热门文章

  1. 云原生社区 meetup 第四期广州站报名中
  2. 视频教程-Linux运维高薪课程-Linux
  3. 液晶面板里面有些什么配件_一张图看懂液晶面板内部结构,竟如此复杂
  4. 移动网络的切换、重选和重定向
  5. wifi信号衰减与距离关系_wifi无线信号传输衰减和距离的关系公式[室内定位]
  6. tomcat报错405
  7. MCR和MRC汇编指令
  8. 如何相对高效解决代码测评、训练过程中遇到的 Bug
  9. 第十六届智能车竞赛全国总决赛究竟该怎么举办讨论中的“混沌”现象
  10. 3小时快速入门html5+css(2022)