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

Linux-线程

目录

1、守护线程

1.1、特点:

1.2.、进程组:

 1.3、会话:

2、线程的概念:

    创建线程后,地址空间没有变化,进程退出变成了,子线程—主线程,创建的子线程和主线程共用地址空间,子线程的PCB是从主线程拷贝来的,主线程-子线程除了栈不共享之外其余全部共享,如果创建5个子线程那么主线程的栈会被分成5份,,总之创建了几个子线程,主线程的栈就会被分成几份;

2.1、多线程和多进程的区别:

3、线程的创建 

3.1、相关函数:

4、线程的分离属性

 4.1、属性:

5、线程同步:

5.1、 数据混乱:当主线程与子线程同时对共享资源进行操作的时候会出现主线程与子线程抢占资源的情况出现,所以需要同步机制来对着各种现象进行干预,确保每个线程能够对共享资源进行操作,不会抢占资源;

5.2、实现线程同步机制的方法:

 6、互斥量(互斥锁)

6.1、属性:

6.2、 特点:

6.3、使用步骤:

6.4、相关函数:

7、原子操作:

7.1、属性:

7.2、实现方法:

7.2、原子操作与互斥锁进行比较:

互斥锁:

总结:通过对上面两种操作的时间对比明显原子操作的性能要优于互斥锁;

8、死锁:

8.1、造成原因:自己锁自己

8.2、两个线程互相阻塞:

9、读写锁

9.1、属性:

9.4、主要操作函数:

 10、条件变量

 10.1、属性:

10.2、两种状态:

10.3、主要函数:

11、信号量:

11.1、类型

11.2、主要函数:

11.3、信号量与互斥锁的区别:

 11.3、实现生产者,消费者模型:

实现思路:


1、守护线程

1.1、特点:

  后台服务进程独立于控制终端周期性执行某任务不受用户登录注销影响一般采用以d结尾名字(服务)

1.2.、进程组:

属性:

进程的组长:组里边的第一进程进程组的ID==进程中的组长的ID进程中组长的选择:进程中的第一个进程进程组ID的设定:进程组的ID就是组长的进程ID

 1.3、会话:

属性:

创建一个会话注意事项:不能是进程组长创建会话的进程成为新进程组的组长:有些lInux版本需要root权限执行此操作创建出的新会话会丢弃原有的控制终端:一般步骤;fork ,父亲死,儿子执行创建会话操作(setid)获取进程所属的会话ID:pid_t getsid(pid_t pid);创建一个会话:pid_t setid(void);

2、线程的概念:

       

    创建线程后,地址空间没有变化,进程退出变成了,子线程—主线程,创建的子线程和主线程共用地址空间,子线程的PCB是从主线程拷贝来的,主线程-子线程除了栈不共享之外其余全部共享,如果创建5个子线程那么主线程的栈会被分成5份,,总之创建了几个子线程,主线程的栈就会被分成几份;

    在Linux下线程是轻量级进程,对内核来说线程就是进程;

2.1、多线程和多进程的区别:

        

名称始终共享的资源
多进程代码、文件描述符、内存映射区--mmap
多线程堆、全局变量、节省资源

3、线程的创建 

3.1、相关函数:
1.创建线程‐‐pthread_create
int pthread_create( pthread_t *thread), //线程ID = 无符号长整型
const pthread_attr_t *attr, //线程属性,NULL
void *(*start_routine)(void *), //线程处理函数
void *arg); //线程处理函数
参数:
pthread:传出参数,线程创建成功之后,会被设置一个合适的值
attr:默认传NULL
start_routine:子线程的处理函数
arg: 回调函数的参数
返回值:
成功:0
错误:错误号 //perror不能使用该函数打印错误信息
主线程先退出,子线程会被强制结束
验证线程直接共享全局变量

 实验:子线程的创建:

#include<stdio.h>
#include<pthread.h>void* myfunc(void *arg){printf("child pthread id:%ld\n",pthread_self());return 0;
}int main()
{pthread_t pthid;pthread_create(&pthid,NULL,myfunc,NULL);printf("parent pthread id:%ld\n",pthread_self());for(int i=0; i<5; i++){printf("i = %d\n",i);}sleep(3);return 0;}
~    

结果:只执行了主函数中的子进程创建的并没有执行,是因为主线程与子线程在相互抢占资源

将主函数中加入休眠的延时动作,再次对文件进行编辑生程运行文件

结果:

2、单个线程退出--pthread_exit函数原型: void pthread‐exit(void *retval);
retval指针:必须指向全局,堆

 实验:

#include<stdio.h>
#include<pthread.h>
#include<string.h>
void* myfunc(void *arg){printf("child pthread id:%ld\n",pthread_self());for(int i=0; i<5 ; i++){printf("child pthread %d\n",i);}return 0;
}int main()
{pthread_t pthid;int ret;ret = pthread_create(&pthid,NULL,myfunc,NULL);if(ret != 0){printf("error number is %d\n",ret);printf("%s\n",strerror(ret));}printf("parent pthread id:%ld\n",pthread_self());pthread_exit(NULL);for(int i=0; i<5; i++){printf("i = %d\n",i);}sleep(3);return 0;}

结果:在使用pthread_exit()函数以后后续的代码并没有用执行;

3、阻塞等待线程退出,获取线程退出状态--pthread_join函数原型:
int pthread_join(pthread_t pthread, void **retval)
参数:
pthread:要回收的子线程的ID
retval:读取线程退出的携带信息
传出参数
void* ptr;
pthread_join(pthid,&ptr);
指向的内存和pthread_exit参数指向地址一致

        注意: pthread_join()函数如果需要携带信息量时需要和pthread_exit()同时使用或return(返回量);

实验:等待子线程退出,阻塞主线程,并将子线程的返回量:“hello”输出;

#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#include <stdlib.h>
void* myfunc(void *arg){char* msg = malloc(64);printf("child pthread id:%ld\n",pthread_self());for(int i=0; i<5 ; i++){printf("child pthread %d\n",i);}strcpy(msg,"hello\n");return (void*)msg;
}int main()
{pthread_t pthid;void* msg = NULL;int ret;ret = pthread_create(&pthid,NULL,myfunc,NULL);if(ret != 0){printf("error number is %d\n",ret);printf("%s\n",strerror(ret));}printf("parent pthread id:%ld\n",pthread_self());pthread_join(pthid,&msg);printf("return is %s",(char*)msg);pthread_exit(NULL);for(int i=0; i<5; i++){printf("i = %d\n",i);}sleep(3);return 0;
}

结果:

4、线程分离--pthread_detach函数原型:int pthread_datach(pthread_t thread);
调用该函数之后不需要 pthread_join
子线程会自动回收自己的PCB
5、杀死线程:函数原型:int pthread_cancel(pthread_t pthread);

注意: 在使用该函数时,线程里面要有一次系统函数的调用:write、read、printf,等;

6、比较两个线程ID是否相等(预留函数)--pthread_equal函数原型:
int pthread_equal(pthread_t t1,pthread_t t2);

4、线程的分离属性

 4.1、属性:

        其决定了线程在终止之后,是否需要被其他线程(比如主线程)回收资源(通过pthread_join());一旦线程在创建时被设置为分离属性,就无法在join;

设置线程分离:pthread_detach(pthread_t thread);在创建时设置为分离属性:pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&tid, &attr, thread_func, NULL);pthread_attr_destroy(&attr);

5、线程同步:

5.1、 数据混乱:当主线程与子线程同时对共享资源进行操作的时候会出现主线程与子线程抢占资源的情况出现,所以需要同步机制来对着各种现象进行干预,确保每个线程能够对共享资源进行操作,不会抢占资源;

实验:主线程,子线程对一个全局变量同时进行相加:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>int counter = 0;void* child_thread(void* arg) {for (int i = 0; i < 10; ++i) {printf("Child Thread: counter = %d\n", counter++);usleep(100000); }return NULL;
}int main() {pthread_t tid;if (pthread_create(&tid, NULL, child_thread, NULL) != 0) {perror("Failed to create thread");return 1;}for (int i = 0; i < 10; ++i) {printf("Main Thread: counter = %d\n", counter++);usleep(100000);  }pthread_join(tid, NULL);return 0;
}

结果:并没有代码想要实现的功能,而是分别交替相加到20;

5.2、实现线程同步机制的方法:

 

 6、互斥量(互斥锁)

6.1、属性

        两个线程分别通过对共享资源上锁,阻塞其他线程,从而保证操作的独立性;操作过程结束后对该资源解锁后,鳍鱼津城才可再次对其进行操作;

6.2、 特点:

        多个线程访问共享数据是串行的;

6.3、使用步骤:

创建互斥锁: pthread_mutex_t mutex;初始化:pthread_mutex_init(&mutex,NULL); -- mutex = 1找到线程共同操作的共享数据加锁:操作共享资源之前加锁,pthread_mutex_lock(&mutex); //阻塞 --mutex = 0pthread_mutex_trylock(&mutex); // 如果锁上锁直接返回,不阻塞    XXXXX共享数据操作 //临界区 ,越小越好解锁:pthread_mutex_unlock(&mutex); // -- mutex = 1阻塞在锁上的线程会被唤醒销毁:pthread_mutex_destory(&mutex);

6.4、相关函数:

初始化互斥锁pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr,);
销毁互斥锁:pthread_mutex_destory(pthread_mutex_t* mutex );
加锁pthread_mutex_lock(pthread_mutex* mutex);mutex:
没有被锁上,当前线程会将这把锁锁上
被锁上了:当前线程阻塞,锁被打开之后,线程解除阻塞
尝试加锁,失败返回,不阻塞pthread_mutex_trylock(pthread_mutex_t* mutex);
没有锁上:当前线程会被这把锁加锁
如果锁上了:不会阻塞,返回
返回0:加锁 成功。没锁上:返回错误号if( pthread_mutex_trylock(& mutex)==0){//尝试加锁,并且成功了//访问共享资源XXXXXXXX}else{//错误处理//或者等待,再次尝试加锁}解锁pthread_mutex_unlock(pthread_mutex_t* mutex);

实验:对线程同步的实验添加互斥锁:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>int counter = 0;//create rwlock
pthread_mutex_t mutex;void* child_thread(void* arg) {//子线程加锁pthread_mutex_lock(&mutex);for (int i = 0; i < 10; ++i) {printf("Child Thread: counter = %d\n", counter++);usleep(100000);  // 模拟工作,100ms}//子线程解锁pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t tid;//初始化互斥锁pthread_mutex_init(&mutex,NULL);// 创建子线程if (pthread_create(&tid, NULL, child_thread, NULL) != 0) {perror("Failed to create thread");return 1;}//主线程加锁pthread_mutex_lock(&mutex);for (int i = 0; i < 10; ++i) {printf("Main Thread: counter = %d\n", counter++);usleep(100000);  // 模拟工作,100ms}//主线程解锁pthread_mutex_unlock(&mutex);// 等待子线程结束pthread_join(tid, NULL);return 0;
}

结果:主线程,子线程执行各自操作后再执行另外操作,没有发生资源抢占;

7、原子操作:

7.1、属性

          一个操作在cpu层面是不可中断的,要么全部执行完,要么完全不执行;即使有多个线程来的访问这个变量,也不会出现数据冲突;

注意:没有一个普通函数能够单独实现原子操作;其实现需要以来硬件和特殊指令;

7.2、实现方法:

        使用GCC内建函数:

__sync_fetch_and_add(&counter, 1);  // 原子加1,返回旧值
__sync_add_and_fetch(&counter, 1);  // 原子加1,返回新值

        为什么它可以实现原子操作: 

7.2、原子操作与互斥锁进行比较:

下面通过计时函数gettimeofday()来对原子操作和互斥函数之间的性能进行一个比较:

原子操作:通过创建两个线程来对counter进行++操作;

#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>int counter = 0;void* thread_func(void* arg) {for (int i = 0; i < 100000; ++i) {__sync_fetch_and_add(&counter, 1);  // 原子操作}return NULL;
}int main() {pthread_t t1, t2;struct timeval start, end;gettimeofday(&start, NULL);  // 开始计时pthread_create(&t1, NULL, thread_func, NULL);pthread_create(&t2, NULL, thread_func, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);gettimeofday(&end, NULL);  // 结束计时long time_us = (end.tv_sec - start.tv_sec) * 1000000L + (end.tv_usec - start.tv_usec);printf("With atomic: counter = %d\n", counter);printf("Elapsed time: %ld microseconds\n", time_us);return 0;
}~                                                                                                                                                                                                          
~                                                                                                                                                                                                          
~                                                                                                                                                                                                          
~                                                                                                                                                                                                          
~                                                                                                                                                                                                          
~                      

原子操作结果:

互斥锁:
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>int counter = 0;
pthread_mutex_t lock;void* thread_func(void* arg) {for (int i = 0; i < 100000; ++i) {pthread_mutex_lock(&lock);counter++;pthread_mutex_unlock(&lock);}return NULL;
}int main() {pthread_t t1, t2;struct timeval start, end;pthread_mutex_init(&lock, NULL);gettimeofday(&start, NULL);  // 开始计时pthread_create(&t1, NULL, thread_func, NULL);pthread_create(&t2, NULL, thread_func, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);gettimeofday(&end, NULL);  // 结束计时long time_us = (end.tv_sec - start.tv_sec) * 1000000L + (end.tv_usec - start.tv_usec);printf("With mutex: counter = %d\n", counter);printf("Elapsed time: %ld microseconds\n", time_us);pthread_mutex_destroy(&lock);return 0;
}

互斥锁结果:

总结:通过对上面两种操作的时间对比明显原子操作的性能要优于互斥锁;

8、死锁:

8.1、造成原因:自己锁自己

示例:

for(int i = 0;i<MAX;i++)
{
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
int crt = number;
crt++;
number = crt;
printf("thread A id = %ld,number = %d\n",pthread_self(),number);
pthread_mutex_unlock(&mutex);
usleep(10);
}

        上面的线程使用了两把锁,但是最后只解开了一把,另一把还在锁着;

8.2、两个线程互相阻塞:

线程1 访问共享资源 B ,对 B 锁加锁 - 线程 1 阻塞在 B锁上
线程2 访问共享资源 A ,对 A 锁加锁 - 线程 2 阻塞在 A锁上
解决方法:

        让线程按照一定顺序去访问共享资源;在访问其他锁得时候需要先把自己的锁解开;

9、读写锁

9.1、属性
1.读写锁是几把锁?一把锁pthread_rwlock_t lock;2.读写锁的类型;读锁-对内存做读操作写锁-对内存做写操作

注意:写锁的优先级要高于读锁,多个进程可以同时拥有读锁,但是写锁不行,写锁只能独占;互斥锁是一个线程对一个资源操作,是串行的,读写锁是并行的可以共享;

9.2、读写锁特性:

1、线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功读共享-并行处理2、线程A加写锁成功,又来了三个线程,做读操作,三个线程阻塞写独占3、线程A加读锁成功,又来了B线程加写锁阻塞,又来了C线程加读锁阻塞读写不可以同时进行写的优先级高

9.3、场景练习:

1、线程A加写锁成功,线程B请求读锁

        线程B阻塞   (写锁优先级高于读锁)

2、线程A持有读锁,线程B请求写锁

        线程B阻塞    (写锁与读锁不能同时存在,需要A进程释放读锁)

3、线程A拥有读写,线程B请求读锁

        线程B阻塞        (写锁优先级高于读锁,需要A进程释放写锁)

4、线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁

        线程B阻塞,线程C阻塞  (需要A释放读锁)

        线程B加锁,线程C阻塞  (B写锁的优先级高于读锁)

        线程C加锁

5、线程A持有写锁,然后线程B请求读锁,然后线程C请求写

        线程B阻塞,线程C阻塞   (需要A释放写锁)

        线程C加锁,线程B阻塞   (写锁优先级高于读锁)

        线程B加锁

9.4、主要操作函数:

初始化读写锁pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,const pthread_rwlockattr_t* restrict attr );销毁读写锁pthread_rwlock_destroy(pthread_rwlock_t* rwlock):加读锁pthread_rwlock_rdlock(pthread_rwlock_t* rdlock);阻塞:之前对这把锁加的是写锁的操作尝试加读锁pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);加锁成功:返回0失败:返回错误号加写锁pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);阻塞:上一次加写锁还没解锁阻塞:上一次加读锁还没解锁尝试加写锁pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);解锁pthread_rwlock_unlock(pthread_rwlock_t* rwlock)

实验:三个线程不定时同时写一个全局变量,五个线程不定时读同一全局资源

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>int number = 1000;// 创建读写锁
pthread_rwlock_t lock;void* write_func(void* arg)
{while (1){pthread_rwlock_wrlock(&lock); number++;printf("write: %ld, %d\n", pthread_self(), number);pthread_rwlock_unlock(&lock);usleep(500000);  }return NULL;
}void* read_func(void* arg)
{while (1){pthread_rwlock_rdlock(&lock); printf("read : %ld, %d\n", pthread_self(), number);pthread_rwlock_unlock(&lock);usleep(500000);}return NULL;
}int main()
{pthread_rwlock_init(&lock, NULL);pthread_t p[8];for (int i = 0; i < 3; i++){pthread_create(&p[i], NULL, write_func, NULL);}for (int i = 0; i < 5; i++){pthread_create(&p[i + 3], NULL, read_func, NULL);}for (int i = 0; i < 8; i++){pthread_join(p[i], NULL);}pthread_rwlock_destroy(&lock);return 0;
}

结果:5个进程读。3个进程写;

 10、条件变量

 10.1、属性:

        条件变量是为了阻塞进程,但它不是锁,需要使用条件变量与互斥量一起使用:

        使用时,互斥量:保护一块共享数据;条件变量:引起阻塞;

10.2、两种状态:

        1、条件不满足:阻塞线程;

        2、条件满足:通知阻塞的线程开始工作;

10.3、主要函数:

初始化一个条件变量pthread_cond_init(pthread_cond_t * restrict cond,const pthread_condattr_t * restrict attr);销毁一个条件变量pthread_cond_destroy(pthread_cond_t * cond);阻塞等待一个条件变量pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t * restrict mutex);阻塞线程
将已经上锁的mutex解锁
该函数解除阻塞,对互斥锁加锁
限时等待一个条件变量pthread_cond_timedwait(pthread_cond_t * restrict cond,pthread_mutex_t * restrict mutex,const struct timespec * restrict abstime);唤醒至少一个阻塞在条件变量上的线程pthread_cond_signal(pthread_cond_t* cond);唤醒全部阻塞在条件变量上的线程pthread_cond_broadcast(pthread_cond_t * cond);

实验:使用条件变量实现生产者,消费者模型

实现思路:

工作流程:

                                              

烧饼----节点

生产者-----生产节点;不断产生节点并使用头插法插入到头节点,流程如下:

创建一个新节点;将节点的next只想原来的头节点,更新头指针,使其指向新节点;

实现代码: 

Node* head = NULL;    Node* new_node = (Node*)malloc(sizeof(Node));new_node->data = value;// 新节点的 next 指向当前头节点new_node->next = head;// 更新头指针,指向新节点head = new_node;

消费者----删除节点

先判断列表是否存在,一旦被唤醒就删除头节点,使用头删法;

实现代码: 

struct Node* temp = head;  // 保存当前头节点
head = head->next;         // 把头指针移动到下一个节点
free(temp);                // 释放原来的头节点

总体实现代码:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>typedef struct node
{int data;struct node* next;
}Node;//create head node
Node* head = NULL;//create mutex
pthread_mutex_t mutex;//create cond
pthread_cond_t cond;void* produce()
{while(1){//create nodeNode* pnew = (Node*)malloc(sizeof(Node));//init nodepnew->data = rand()%1000;//lockpthread_mutex_lock(&mutex);pnew->next = head;head = pnew;printf("produce: %ld,%d\n",pthread_self(),pnew->data);//unlockpthread_mutex_unlock(&mutex);}sleep(500);return NULL;}
void* customer()
{while(1){if(head==NULL){continue;}//delete head nodeNode* pdel = head;head = head->next;printf("customer: %ld,%d\n",pthread_self(),pdel->data);free(pdel);}sleep(500);return NULL;}int main(){pthread_t p1,p2;pthread_mutex_init(&mutex,NULL);pthread_cond_init(&cond,NULL);pthread_create(&p1,NULL,produce,NULL);pthread_create(&p2,NULL,customer,NULL);pthread_join(p1,NULL);pthread_join(p2,NULL);return 0;}

结果:

11、信号量:

11.1、类型

sem_t sem;
加强版的互斥锁

11.2、主要函数:

初始化信号量sem_init(sem_t *sem,int pshared,unsigned int value);0-线程同步1-进程同步value-最多有几个线程操作共享数据销毁信号量sem_destroy(sem_t *sem);加锁sem_wait(sem_t *sem);调用一次相当于对sem做了一次 -- 操作如果sem值为0,线程会阻塞尝试加锁sem_trywait(sem_t *sem);sem == 0;加锁失败,不阻塞,直接发牛限时尝试加锁sem_timewait(sem_t *sem,xxxx);解锁++sem_post(sem_t *sem);对sem做了++ 操作

11.3、信号量与互斥锁的区别:

        互斥锁:串行操作

    信号量:并行操做

 11.3、实现生产者,消费者模型:

实现思路:

实现代码: 

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>
typedef struct node
{int data;struct node* next;
}Node;//create head node
Node* head = NULL;//create mutex
pthread_mutex_t mutex;//create cond
pthread_cond_t cond;sem_t producer_sem;
sem_t customer_sem;void* produce()
{while(1){sem_wait(&producer_sem);//create nodeNode* pnew = (Node*)malloc(sizeof(Node));//init nodepnew->data = rand()%1000;pnew->next = head;head = pnew;printf("produce: %ld,%d\n",pthread_self(),pnew->data);sem_post(&customer_sem);}sleep(500);return NULL;}
void* customer()
{while(1){sem_wait(&customer_sem);//delete head nodeNode* pdel = head;head = head->next;printf("customer: %ld,%d\n",pthread_self(),pdel->data);free(pdel);sem_post(&producer_sem);}sleep(500);return NULL;}int main(){pthread_t thid[2];sem_init(&producer_sem,0,4);sem_init(&customer_sem,0,0);pthread_create(&thid[0],NULL,produce,NULL);pthread_create(&thid[1],NULL,customer,NULL);for(int i=0;i<2;i++){pthread_join(thid[i],NULL);}sem_destroy(&producer_sem);sem_destroy(&customer_sem);return 0;}

结果:

http://www.xdnf.cn/news/14168.html

相关文章:

  • Component name “index“ should always be multi-word的解决方式
  • HarmonyOS应用开发——线性布局
  • python爬虫简便框架,附带百度操作完整案例
  • Transformer 核心概念转化为夏日生活类比
  • 自主导航巡检机器人系统解决方案
  • [智能客服project] 架构 | 对话记忆 | 通信层
  • UR机器人解锁关节扭矩控制:利用英伟达Isaac Lab框架,推动装配自动化的Sim2Real迁移
  • 自适应攻击的强大后门防御
  • 【AT32】AT32定时器
  • 【华为Pura 80 Ultra影像真的有点东西】
  • 批处理实现:自动抓取perfetto日志 自动导出到当前文件夹 自动打开分析页面
  • NLP学习路线图(四十四):跨语言NLP
  • 【Linux基础知识系列】第二十四篇-网络配置文件的解析与修改
  • error: error:0308010c:digital envelope routines::unsupported
  • 联想笔记本怎么装win11专业版_联想笔记本用u盘装win11专业版图文教程
  • 【BrowserTools MCP:让 AI 直接调试你的网页应用】
  • 深度学习笔记26-天气预测(Tensorflow)
  • 光伏功率预测 | RF随机森林多变量单步光伏功率预测(Matlab完整源码和数据)
  • react react-router-dom中获取自定义参数v6.4版本之后
  • 使用大模型预测甲状旁腺恶性肿瘤的研究报告
  • 2025年6月英语四级CET-4作文预测10篇7页PDF
  • 电路图识图基础知识-电动机的保护电路保护方式(二十六)
  • (题目向,随时更新)动态规划算法专题(2) --见识常见的尝试模型
  • centos 8.3(阿里云服务器)mariadb由系统自带版本(10.3)升级到10.6
  • AI与机器学习ML:利用Python 从零实现神经网络
  • 科技新底座揭幕!2025 MWC上海锚定AI+、5G融合、双区创新三大引擎
  • 扩展模块--QWebEngine功能及架构解析
  • XPath 注入与修复
  • 通过SMS凭据管理系统,实现数据库密码、服务器密码、Token等机密信息的临时授权和安全合规使用
  • 【unitrix】 1.5 Unitrix库结构和设计意图(lib.rs)