【Linux/C++多线程篇(二) 】给线程装上“红绿灯”:通俗易懂的同步互斥机制讲解 & C++ 11下的多线程

【Linux/C++多线程篇(二) 】给线程装上“红绿灯”:通俗易懂的同步互斥机制讲解 & C++ 11下的多线程

⭐️在这个怀疑的年代,我们依然需要信仰

个人主页:YYYing.

⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】

系列上期内容:【Linux/C++多线程篇(一) 】多线程编程入门

系列下期内容:【Linux/C++网络篇(一) 】网络编程入门


目录

前言:当多线程遇上“交通混乱”

线程的同步互斥机制

一、为什么需要同步互斥?

二、线程互斥之互斥锁

2.1、互斥锁的相关API函数接口

 📖 创建一个互斥锁

 📖 初始化互斥锁

📖 获取锁资源

📖 释放锁资源

📖 销毁互斥锁

2.2、互斥锁的小练习

三、线程同步之无名信号量

3.1、无名信号量的相关API函数接口

 📖 创建无名信号量

 📖 初始化无名信号量

 📖 申请无名信号量的资源(P操作)

 📖 释放无名信号量的资源(V操作)

 📖 销毁无名信号量

3.2、互斥锁的小练习

四、线程同步之条件变量

4.1、条件变量的API函数接口

 📖 创建一个条件变量

 📖 初始化条件变量

 📖 消费者线程进入等待队列

 📖 生产者线程唤醒休眠队列中的任务

 📖 销毁条件变量

3.2、条件变量的小练习

C++11中的多线程

一、线程相关常用操作

1.1、线程的创建

1.2、线程体函数种类

1.3、线程号获取

1.4、线程号回收

二、互斥锁的使用

2.1、常用函数

2.2、lock_guard的使用

2.3、代码演示

三、条件变量

2.1、常用函数

2.2、代码演示

结语

---⭐️封面自取⭐️---



前言:当多线程遇上“交通混乱”

        想象一下,你是一个繁忙路口的交警,需要同时指挥四面八方的车辆。如果没有红绿灯和交警,所有车辆都凭感觉开,结果必然是撞车、拥堵、混乱。在计算机世界里,多个线程同时访问共享数据时,也会出现类似的“交通事故”——数据错乱、程序崩溃、结果不可预测

        为了让线程们有序地工作,我们需要给它们装上“红绿灯”,也就是同步互斥机制。本文将从生活比喻出发,带你轻松理解这些看似复杂的并发工具。

线程的同步互斥机制

一、为什么需要同步互斥?

假设有一个火车票售票系统,剩余票数为 1。两个线程同时执行以下操作

if (tickets > 0) { tickets--; cout << "购票成功"; }

        如果两个线程同时检查 tickets > 0,发现都是 1,于是都执行 tickets--,结果票数变成 -1,但两人都以为自己买到了票。这就是典型的竞态条件——程序的结果依赖于线程执行的偶然顺序。

        这种问题源于线程的交错执行tickets-- 不是原子操作(也就是指不会被线程调度机制打断的操作。),它实际上分为三步:读取、减一、写回。如果两个线程的步骤交错,就会出错。

        要解决这个问题,我们需要确保同一时刻只有一个线程能操作票数,这就是互斥。同时,可能还需要让一个线程等待另一个线程完成某件事(比如等票补足再卖),这就是同步

        总的来说——由于同一个进程的多个线程会共享进程的资源,这些被共享的资源称为临界资源,多个线程对公共资源的抢占问题,访问临界资源的代码段称为临界区,多个线程抢占进程资源的现象称为竞态,为了解决竞态,我们引入了同步互斥机制

下面,我们就来看看系统为我们提供的几种“红绿灯”。


二、线程互斥之互斥锁

        互斥锁的本质是一个特殊的临界资源,当该临界资源被某个线程所拥有后,其他线程就不能拥有该资源,直到,拥有该资源的线程释放掉互斥锁后,其他线程才能进行抢占(同一时刻,一个互斥锁只 能被一个线程所拥有),相当于一把获取资源的钥匙。

2.1、互斥锁的相关API函数接口

 📖 创建一个互斥锁

        只需定义一个pthread_mutex_t 类型的变量即创建了一个互斥锁

pthread_mutex_t mutex;

 📖 初始化互斥锁
函数原型

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;       //静态初始化

头文件iostream
功能初始化互斥锁变量
参数说明

参数1:互斥锁变量的地址,属于地址传递

参数2:互斥锁属性,一般填NULL,让系统自动设置互斥锁属性

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

📖 获取锁资源
函数原型int pthread_mutex_lock(pthread_mutex_t *mutex);
头文件iostream
功能获取锁资源,如果要获取的互斥锁已经被其他线程锁定,那么该函数会阻塞,直到能够获取锁资源
参数说明互斥锁地址,属于地址传递
返回值成功返回0,失败返回错误码

📖 释放锁资源
函数原型int pthread_mutex_unlock(pthread_mutex_t *mutex);
头文件iostream
功能释放对互斥锁资源的拥有权
参数说明互斥锁变量的地址
返回值成功返回0,失败返回错误码

📖 销毁互斥锁
函数原型int pthread_mutex_destroy(pthread_mutex_t *mutex);
头文件iostream
功能销毁互斥锁
参数说明互斥锁变量的地址
返回值成功返回0,失败返回错误码

2.2、互斥锁的小练习

        可以看到我们线程1和线程2都是先抢占我们锁资源然后进行释放,这其中的机制依旧是时间片轮询上下文切换。

#include<iostream> #include<cstdio> #include<cstring> #include <unistd.h> using namespace std; //11、创建一个互斥锁 pthread_mutex_t mutex; //定义一个全局资源 int num = 520; //定义分支线程1 void *task1(void *arg){ while(1){ sleep(1); //临界资源 //33、获取锁资源 pthread_mutex_lock(&mutex); num -= 10; //线程1将临界资源减少10 printf("张三取了10,剩余%d\n", num); //44、释放锁资源 pthread_mutex_unlock(&mutex); } } //定义分支线程2 void *task2(void *arg){ while(1){ sleep(1); //33、获取锁资源 pthread_mutex_lock(&mutex); num -= 20; //线程1将临界资源减少10 printf("李四取了20,剩余%d\n", num); //44、释放锁资源 pthread_mutex_unlock(&mutex); } } /*****************************主线程****************************/ int main() { //22、初始化互斥锁,参数NULL表示让系统自动分配互斥锁属性 pthread_mutex_init(&mutex, NULL); //1、创建两个分支线程 pthread_t tid1,tid2; if(pthread_create(&tid1, NULL, task1, NULL) != 0){ printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0){ printf("tid2 create error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x\n", tid1, tid2); //2、阻塞等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); //55、释放锁资源 pthread_mutex_destroy(&mutex); std::cout << "Hello, World!" << std::endl; return 0; }

三、线程同步之无名信号量

        线程同步:就是多个线程之间有先后顺序得执行,这样在访问临界资源时,就不会产生抢占现象了

        同步机制常用于生产者消费者模型:消费者任务要想执行,必须先执行生产者线程,多个任务有顺序执行

        无名信号量:本质上也是一个特殊的临界资源,内部维护了一个value值,当某个进行想要执行之前,先申请该无名信号量的value资源,如果value值大于0,则申请资源函数接触阻塞,继续执行后续操作。如果value值为0,则当前申请资源函数会处于阻塞状态,直到其他线程将该value值增加到大于0

3.1、无名信号量的相关API函数接口

 📖 创建无名信号量

        只需定义一个sem_t 类型的变量即可

sem_t sem;

 📖 初始化无名信号量
函数原型

int sem_init(sem_t *sem, int pshared, unsigned int value);

头文件semaphore.h
功能初始化无名信号量,最主要是初始化value值
参数说明

参数1:无名信号量的地址

参数2:判断进程还是线程的同步

               0:表示线程间同步

               非0:表示进程间同步,需要创建在共享内存段中

参数3:无名信号量的初始值

返回值成功返回0,失败返回-1并置位错误码

 📖 申请无名信号量的资源(P操作)
函数原型int sem_wait(sem_t *sem);
头文件semaphore.h
功能阻塞申请无名信号量中的资源,成功申请后,会将无名信号量的value进行减1操作,如果当前无名信号量的value为0,则阻塞
参数说明无名信号量的地址
返回值成功返回0,失败返回-1并置位错误码

 📖 释放无名信号量的资源(V操作)
函数原型int sem_post(sem_t *sem);
头文件semaphore.h
功能将无名信号量的value值增加1操作
参数说明无名信号量的地址
返回值成功返回0,失败返回-1并置位错误码

 📖 销毁无名信号量
函数原型int sem_destroy(sem_t *sem);
头文件semaphore.h
功能销毁无名信号量
参数说明无名信号量的地址
返回值成功返回0,失败返回-1并置位错误码

3.2、互斥锁的小练习

        我们现在再来看看同步在生产者消费者模型中的应用,当 进程2 想要执行之前,先申请无名信号量的value资源,如果value值大于0,则申请资源函数解除阻塞,并继续执行后续操作。如果value值为0,则当前申请资源函数会处于阻塞状态,直到 线程1 将该value值增加到大于0

#include<iostream> #include<cstdio> #include<cstring> #include<unistd.h> #include<semaphore.h> #include<pthread.h> sem_t sem; //创建生产者线程 void *task1(void *arg){ int num = 5; while(num--){ sleep(1); printf("我生产了一辆特斯拉\n"); //44、释放无名信号量资源 sem_post(&sem); } //退出线程 pthread_exit(NULL); } //创建消费者线程 void *task2(void *arg){ int num = 5; while(num--){ //33、申请无名信号量的资源 sem_wait(&sem); printf("我消费了一辆特斯拉,很开心\n"); } //退出线程 pthread_exit(NULL); } /*******************************主程序*************************/ int main() { //22、初始化无名信号量,第一个0表示用于线程间通信,第二个0表示初始值为0 sem_init(&sem, 0, 0); //1、创建两个分支线程 pthread_t tid1,tid2; if(pthread_create(&tid1, NULL, task1, NULL) != 0){ printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0){ printf("tid2 create error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x\n", tid1, tid2); //2、阻塞等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); //55、销毁无名信号量 sem_destroy(&sem); return 0; }

四、线程同步之条件变量

        我们不难发现,如果我们只用互斥锁,那么我们的消费者和生产者只能是一对一的关系,但我们要想让一个生产者对应多个消费者用互斥锁就不行了,所以我们就要用到我们的条件变量了。

        条件变量本质上也是一个临界资源,他维护了一个队列,当消费者线程想要执行时,先进入队列中等待生产者的唤醒。执行完生产者,再由生产者唤醒在队列中的消费者,这样就完成了生产者和消费者之间的同步关系。

        但是,多个消费者在进入休眠队列的过程是互斥的,所以,在消费者准备进入休眠队列时,我们需要使用互斥锁来进行互斥操作。

4.1、条件变量的API函数接口

 📖 创建一个条件变量

        只需定义一个pthread_cond_t类型的全局变量即可

pthread_cond_t cond;

 📖 初始化条件变量
函数原型

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;     //静态初始化

头文件iostream
功能初始化条件变量
参数说明

参数1:条件变量的起始地址

参数2:条件变量的属性,一般填NULL

返回值

成功返回0,失败返回一个错误码


 📖 消费者线程进入等待队列
函数原型

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

头文件iostream
功能将线程放入休眠等待队列,等待其他线程的唤醒
参数说明

参数1:条件变量的地址

参数2:互斥锁,由于多个消费者线程进入等待队列时会产生竞态,为了解决竞态,需要使用一个互斥锁

返回值

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


 📖 生产者线程唤醒休眠队列中的任务
函数原型int pthread_cond_broadcast(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond);
头文件iostreamiostream
功能唤醒条件变量维护的队列中的所有消费者线程唤醒条件变量维护的队列中的第一个进入队列的消费者线程
参数说明条件变量的地址条件变量的地址
返回值

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

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

 📖 销毁条件变量
函数原型int pthread_cond_destroy(pthread_cond_t *cond);
头文件iostream
功能销毁一个条件变量
参数说明条件变量的地址
返回值

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


3.2、条件变量的小练习

        我们再以生产者消费者模型来看看我们的用法,我们既可以一个一个唤醒,也可以直接一次性将消费者全部唤醒。

#include<iostream> #include<unistd.h> #include<pthread.h> //11、定义一个条件变量 pthread_cond_t cond; //111、定义一个互斥锁 pthread_mutex_t mutex; //创建生产者线程 void *task1(void *arg) { /* int num = 3; while(num--) { sleep(1); printf("%#x:生产了一辆特斯拉\n", pthread_self()); //44、唤醒一个消费者进行消费 pthread_cond_signal(&cond); } */ sleep(3); printf("我生产了3辆特斯拉\n"); //44、唤醒所有消费者线程 pthread_cond_broadcast(&cond); //退出线程 pthread_exit(NULL); } //创建消费者线程 void *task2(void *arg) { //333、获取锁资源 pthread_mutex_lock(&mutex); //33、进入休眠队列,等待生产者的唤醒 pthread_cond_wait(&cond, &mutex); printf("%#x:消费了一辆特斯拉,很开心\n", pthread_self()); //444、释放锁资源 pthread_mutex_unlock(&mutex); //退出线程 pthread_exit(NULL); } int main() { //22、初始化条件变量 pthread_cond_init(&cond, NULL); //222、初始化互斥锁 pthread_mutex_init(&mutex, NULL); //1、创建两个分支线程 pthread_t tid1,tid2,tid3,tid4; if(pthread_create(&tid1, NULL, task1, NULL) != 0){ printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0){ printf("tid2 create error\n"); return -1; } if(pthread_create(&tid3, NULL, task2, NULL) != 0){ printf("tid3 create error\n"); return -1; } if(pthread_create(&tid4, NULL, task2, NULL) != 0){ printf("tid4 create error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x, tid3 = %#x, tid4 = %#x\n", tid1, tid2, tid3, tid4); //2、阻塞等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_join(tid4, NULL); //55、销毁条件变量 pthread_cond_destroy(&cond); ///555、销毁互斥锁 pthread_mutex_destroy(&mutex); return 0; }

C++11中的多线程

        C++11之后就支持线程支持库了,也支持线程创建、互斥锁、条件变量,线程支持库需要引入头文件 #include<thread>

一、线程相关常用操作

1.1、线程的创建

        C++线程支持库,本质是是面向对象的操作,可以使用构造函数完成。


1.2、线程体函数种类

  • 可以是任意类型的函数,不必要是 void * 类型参数也是void *类型
  • 可以是全局函数,也可以是类中成员函数当做线程体
  • 可以是仿函数当作线程体函数
  • 也可以是Lambda表达式当作线程体函数

1.3、线程号获取

this_thread::get_id()

        下述代码为我们展示了4种不同的线程体函数。

#include<iostream> #include <thread> using namespace std; /*****************第一个测试线程体*******************/ void ThreadFun_1(){ cout<<"ThreadFun_1 tid = "<< this_thread::get_id()<<endl; cout << "ThreadFun_1 test"<<endl; } /*****************第二个线程体测试**********************/ void ThreadFun_2(int num, string str){ //有参无返回值函数 cout<<"ThreadFun_2 tid = "<< this_thread::get_id()<<endl; cout<<"num = " << num << " str = " << str <<endl; } /*******************第三个线程体测试*********************/ class ThreadClass{ public: string name; int age; void ThreadClassFun(){ //类中成员函数作为线程体函数 cout<<"ThreadFun_3 tid = "<< this_thread::get_id()<<endl; cout << "name = "<<this->name<<" age = "<<age<<endl; } }; /**********************主程序**********************/ int main(int argc, const char *argv[]){ //主程序就是主线程 //创建第一个分支线程,使用无参函数完成线程体为无参无返回值函数 thread th1(ThreadFun_1); //创建第二个分支线程,并向线程体中传递数据 string name = "zpp"; thread th2(ThreadFun_2, 520, name); //此时就创建了一个分支线程,线程体函数 //可以直接向线程体传递参数,有多少可以传多少,无需使用结构体完成 //创建第三个分支线程,向线程体中传递一个类的成员函数 ThreadClass test; test.name = "zhangsan"; test.age = 18; thread th3(&ThreadClass::ThreadClassFun, &test); /************lambda表达式当作线程体:开发过程中用的比较多的************/ //创建第四个分支线程,将lambda表达式当作线程体函数 thread th4([](int key){ cout<<"ThreadFun_4 tid = "<< this_thread::get_id()<<endl; cout<<"key = "<<key<<endl; }, 999); //阻塞回收分支线程 th1.join(); th2.join(); th3.join(); th4.join(); return 0; }

1.4、线程号回收

  • 阻塞方式回收线程
th1.join();
  • 非阻塞方式回收线程
th1.detach();

        此处的阻塞非阻塞回收与我们上一篇讲的并无二异,说到底,这些类的实现本质也是在用我们上节课c语言的那些函数所做出来的。

#include<iostream> #include<thread> using namespace std; /*****************第一个测试线程体*******************/ void ThreadFun_1(){ // 无参无返回值 for (int i = 0; i < 10; i++){ cout << "ThreadFun_1 tid = " << this_thread::get_id() << endl; cout << "ThreadFun_1 test" << endl; //延时函数 this_thread::sleep_for(1s); //等待1秒时间 } } /**********************主程序**********************/ int main(int argc, const char *argv[]){ // 阻塞回收分支线程 //th1.join(); // 主程序就是主线程 // 创建第一个分支线程,使用无参函数完成线程体 thread th1(ThreadFun_1); // 此时就创建了一个分支线程,线程体函数为无参无返回值函数 th1.detach(); //将线程设置成分离态:主线程可以继续做自己其他事情 this_thread::sleep_for(20s); return 0; }

二、互斥锁的使用

        互斥锁本质上是完成将多个线程使用临界资源时,防止竞态

        我们需要在c++编程中需要引入头文件 #include<mutex>

2.1、常用函数

1、构造函数:创建一个互斥锁对象 2、 lock():上锁 3、 unlock():释放锁资源

2.2、lock_guard的使用

        但在这其中,我们的mutex互斥锁经常会与lock_guard进行一起使用,用于在其构造时自动获取锁,在析构时自动释放锁。使用 std::lock_guard 的好处是,当 std::lock_guard 对象离开其作用域时,会自动调用析构函数,该析构函数会释放锁。这确保了在任何情况下(包括由于异常等原因导致的提前退出),锁都会被正确释放,从而避免了忘记手动释放锁而导致的死锁问题

std::mutex myMutex; std::lock_guard<std::mutex> lock(myMutex);

2.3、代码演示

#include<iostream> #include<thread> #include<mutex> using namespace std; mutex mux; //实例化一个互斥锁 /*****************第一个测试线程体*******************/ void ThreadFun_1(){ // 无参无返回值 mux.lock(); //获取锁资源 cout<<"======================================"<<endl; cout<<"tid = "<<this_thread::get_id()<<endl; this_thread::sleep_for(1s); cout<<"**************************************"<<endl; mux.unlock(); //释放锁资源 } /**********************主程序**********************/ int main(int argc, const char *argv[]){ for(int i=0; i<10; i++){ thread th(ThreadFun_1); th.detach(); } //线程分离 this_thread::sleep_for(20s); std::cout << "Hello, World!" << std::endl; this_thread::sleep_for(20s); return 0; }

三、条件变量

        实现一个生产者对应多个消费者问题,需要引入头文件:#include<condition_variable>

2.1、常用函数

1、 构造函数:创建并初始化一个条件变量 2、 wait():将消费者线程放入等待队列中 3、 唤醒线程: cv.notify_one(); 唤醒一个线程 cv.notify_all(); 唤醒所有线程

2.2、代码演示

        不难看出与我们刚才所讲的同步机制中的条件变量逻辑是非常相似的。

#include<iostream> #include<thread> #include<mutex> #include<condition_variable> using namespace std; //定义一个条件变量 condition_variable cv; mutex mux; //条件变量头文件 //线程支持库头文件 //互斥锁 //用于防止竞态的互斥锁 //定义生产者线程 void ThreadWrite(){ for(int i=0; i<5; i++){ this_thread::sleep_for(2s); cout<<"我生产了一辆特斯拉"<<endl; cv.notify_one(); //通知一个线程可以消费了 //通知所有线程 //cv.notify_all(); } } //定义消费者线程 void ThreadRead(){ //提前先进入消费者队列 unique_lock<mutex> lock(mux); cv.wait(lock); cout<<"我消费了一辆特斯拉"<<endl; lock.unlock(); // 解锁 } int main(int argc, const char *argv[]) { //创建生产者线程 thread th1(ThreadWrite); //每隔两秒时间生产一辆特斯拉 //创建多个消费之 for(int i=0; i<5; i++){ thread th2(ThreadRead); th2.detach(); } th1.join(); //阻塞回收线程 return 0; }

结语

        当你掌握了“红绿灯”和“独木桥”的原理,你就已经跨过了多线程编程最危险的那道门槛。接下来,就是去享受多核 CPU 带来的速度激情吧!

我是YYYing,后面还有更精彩的内容,希望各位能多多关注支持一下主包。

无限进步,我们下次再见!


---⭐️封面自取⭐️---

Read more

使用GpuGeek高效完成LLaMA大模型微调:实践与心得分享

使用GpuGeek高效完成LLaMA大模型微调:实践与心得分享

使用GpuGeek高效完成LLaMA大模型微调:实践与心得分享 🌟嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 随着大模型的发展,越来越多的AI开发者开始尝试对开源模型进行微调,以适配垂直场景需求。但由于训练资源昂贵、部署过程繁琐,很多人仍止步于“想做”阶段。 本文将结合我在 GpuGeek 平台 上对 LLaMA 模型的微调实践,分享完整流程、调优经验以及平台带来的优势,帮助更多开发者低门槛开启大模型实践之路。 注册链接:https://gpugeek.com/login?invitedUserId=753279959&source=invited 一、选型与准备 选择模型:LLaMA-7B Meta发布的LLaMA系列模型在性能与资源消耗之间取得了不错的平衡,适合作为个人或中小团队的定制基础模型。我选择了 LLaMA-7B,结合LoRA方法进行微调。 选择平台:GpuGeek 为什么选GpuGeek? ✅ 显卡资源充足、节点丰富:支持多种高性能GPU,

By Ne0inhk
10款2026年主流降ai率工具深度测评(含免费降ai率方案),亲测AIGC从88%降至10%以下

10款2026年主流降ai率工具深度测评(含免费降ai率方案),亲测AIGC从88%降至10%以下

说实话,降AI率这事儿真没那么玄乎,找对路子就是降维打击。本人作为过来人,当年的论文降ai也是一把过,稳得连盲审专家都挑不出刺。 最近好多学弟学妹拿着红得发紫的查重报告来哭诉,问我到底咋办?真相只有一个——别死磕脑子,要善用科技! 在这个全员ai降ai的内卷时代,纯靠手改既低效又容易改出语病。今天我就把压箱底的十款高效降ai率工具全盘托出,无论你是想找免费降ai率的野路子,还是在寻觅靠谱的专业降ai率工具,甚至是aigc免费降重的实操指令,这篇合集都能喂饱你。哪怕你现在的AI率飙到90%,也能帮你轻松降低ai,实现极限翻盘! 一、笔灵AI 这真的是我心中的“白月光”,也是我最后定稿时用的“保命符”。 它不是那种简单的词汇替换工具,而是一款专业的论文降ai神器。核心技术是结构级重组,专门对标知网、维普的算法。 传送门:https://ibiling.cn/paper-pass?from=ZEEKLOGjiangaiych113(建议复制链接到电脑浏览器打开,体验更佳!) 我之前踩过最大的坑,就是降ai后论文格式炸了,调格式调通宵。但笔灵最让我瑞思拜的一点是:它能【100%完

By Ne0inhk
AIGC时代编程新宠!如何让孩子通过DeepSeek成为未来的编程大师?

AIGC时代编程新宠!如何让孩子通过DeepSeek成为未来的编程大师?

文章目录 * 一、激发编程兴趣:从游戏开始 * 二、个性化学习计划:DeepSeek的智能推荐 * 三、项目式学习:动手实践,学以致用 * 四、AI精准辅导:即时解答,深度学习 * 五、全面发展:平衡技术与人文 * 六、家长的陪伴与鼓励 * 《信息学奥赛一本通关》 * 本书定位 * 内容简介 * 作者简介 * 目录 在AIGC(Artificial Intelligence Generative Content,人工智能生成内容)技术蓬勃发展的今天,教育领域正经历一场深刻的变革。DeepSeek作为一款由杭州深度求索人工智能基础技术研究有限公司倾力打造的大语言模型工具,正以其卓越的性能和广泛的应用前景,在编程教育领域大放异彩。 一、激发编程兴趣:从游戏开始 孩子的兴趣是学习的最好驱动力。DeepSeek能够生成一系列基于AI的互动编程游戏,这些游戏通过简单的拖拽式编程界面,让孩子在玩乐中学习编程基础。 示例游戏:制作一个简单的“躲避障碍”小游戏 // 使用Scratch风格的伪代码说明 when green

By Ne0inhk
自回归生成:AI写作文,居然是“边想边写”?

自回归生成:AI写作文,居然是“边想边写”?

文章目录 * 前言 * 一、先破个迷:AI写东西,不是“一口气写完”的 * 二、超通俗拆解:自回归生成的完整流程 * 三、为什么它能越写越顺?秘密藏在“注意力”里 * 四、AI写词也会“纠结”:概率选择,不是死答案 * 五、自回归生成,撑起了整个生成式AI的世界 * 1. AI聊天对话 * 2. 文章写作/续写 * 3. 代码自动补全 * 4. 机器翻译 * 5. 文案生成、标题生成、摘要生成 * 6. 多模态生成(文字生图、图生文) * 六、自回归 vs 掩码模型:一文看懂俩兄弟的区别 * 七、2026年的自回归生成:已经进化到什么程度? * 1.

By Ne0inhk