Linux系统编程---线程同步

news2024/11/24 17:57:16

一、同步概念

同步即协同步调,按预定的先后次序运行。

协同步调,对公共区域数据【按序】访问,防止数据混乱,产生与时间有关的错误。

数据混乱的原因:

  1. 资源共享(独享资源则不会)
  2. 调度随机(意味着数据访问会出现竞争)
  3. 线程间缺乏必要同步机制

二、锁

1. 互斥锁

linux中提供一把互斥锁mutex(也称之为互斥量)。

建议锁!对公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但锁本身不具备强制性。

数据共享导致的混乱pthrd_shared.c

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

//子线程
void *tfn(void *arg)
{
        srand(time(NULL));

        while(1)
        {
                printf("hello ");
                sleep(rand() % 3); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
                printf("world\n");
                sleep(rand() % 3);
        }
        return NULL;
}

int main(void)
{
        pthread_t tid;
        srand(time(NULL));

        // 创建线程
        pthread_create(&tid,NULL,tfn,NULL);
        while(1)
        {
                printf("HELLO ");
                sleep(rand() % 3);
                printf("WORLD\n");
                sleep(rand() % 3);
        }

        //回收线程
        pthread_join(tid,NULL);

        return 0;
}

 输出为:

HELLO hello WORLD
world
hello HELLO WORLD
world
HELLO hello WORLD
world
hello HELLO world
hello WORLD
world
HELLO hello world

pthread_mutex_函数

初始化:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

销毁:int pthread_mutex_destroylock(pthread_mutex_t *mutex);

上锁:int pthread_mutex_lock(pthread_mutex_t *mutex);

try锁:int pthread_mutex_trylock(pthread_mutex_t *mutex);

解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);

以上函数的返回值都是:成功返回0,失败返回错误号;

restrict关键字

用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。

使用mutex(互斥量、互斥锁)一般步骤:

  1. pthread_mutex_t lock;创建锁
  2. pthread_mutex_init;初始化
  3. pthread_mutex_lock;加锁
  4. 访问共享数据
  5. pthread_mutex_unlock;解锁
  6. pthread_mutex_destroy;销毁锁

初始化互斥量:

pthread_mutex_t mutex;

1. 动态初始化:pthread_mutex_init(&mutex, NULL);

2. 静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

注意事项:

1. 尽量保证锁的粒度,越小越好。(访间共享数据前,加锁。访问结束【立即】解锁。)
2. 互斥锁:本质是结构体。我们可以看成整数。初值为1。(pthread_mutex_init(函数调用成功。))

3. 加锁:--操作,阻塞线程。
4. 解锁:++操作,换醒阻塞在锁上的线程。
5. try锁:尝试加锁,成功--。失败,返回。同时设置错误号EBUSY

修改上面pthrd_shared.c的代码,使用锁实现互斥访问共享区:        

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

pthread_mutex_t mutex;//定义一把互斥锁

//子线程
void *tfn(void *arg)
{
        srand(time(NULL));

        while(1)
        {
                pthread_mutex_lock(&mutex);//加锁
                printf("hello ");
                sleep(rand() % 3);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
                printf("world\n");
                pthread_mutex_unlock(&mutex);//解锁
        
                sleep(rand() % 3);
        }
        return NULL;
}
         
int main(void)
{
        pthread_t tid;
        srand(time(NULL));
        int ret = pthread_mutex_init(&mutex,NULL);//初始化互斥锁
        if (ret != 0)
        {
                fprintf(stderr,"mutex init error:%s\n",strerror(ret));
                exit(1);        
        }

        // 创建线程
        pthread_create(&tid,NULL,tfn,NULL);
        while(1)
        {
                pthread_mutex_lock(&mutex);//加锁
                printf("HELLO ");
                sleep(rand() % 3);
                printf("WORLD\n");
                pthread_mutex_unlock(&mutex);//解锁
                sleep(rand() % 3);
        }

        //回收线程
        pthread_join(tid,NULL);

        pthread_mutex_destroy(&mutex);//销毁互斥锁
        return 0;
}

 输出为:

HELLO WORLD
hello world
HELLO WORLD
hello world
HELLO WORLD
hello world

2. 死锁:对锁使用不恰当导致的现象

1. 对一个锁反复lock。

2. 两个线程,各自持有一把锁,请求另一把。

情况 1:对一个锁反复 lock

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

pthread_mutex_t mutex;

void *lockTwice(void *arg) 
{
    pthread_mutex_lock(&mutex); // 第一次获取锁
    printf("Lock acquired once.\n");
    pthread_mutex_lock(&mutex); // 尝试再次获取同一个锁,导致死锁
    printf("Lock acquired twice.\n");
    pthread_mutex_unlock(&mutex);
    pthread_mutex_unlock(&mutex);

    return NULL;
}

int main() 
{
    pthread_t tid;
    pthread_mutex_init(&mutex, NULL);
    
    pthread_create(&tid, NULL, lockTwice, NULL);
    pthread_join(tid, NULL);

    pthread_mutex_destroy(&mutex);
    return 0;
}

在这个示例中,我们尝试在同一线程中两次锁定同一个互斥锁。因为 pthread_mutex_t 默认是非递归的,第二次尝试锁定会导致线程阻塞,从而产生死锁 

情况 2:两个线程,各自持有一把锁,请求另一把

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

pthread_mutex_t lock1, lock2;

void *thread1Func(void *arg) 
{
    pthread_mutex_lock(&lock1);
    printf("Thread 1 acquired lock 1\n");
    sleep(1); // 增加死锁发生的可能性
    pthread_mutex_lock(&lock2);
    printf("Thread 1 acquired lock 2\n");
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    return NULL;
}

void *thread2Func(void *arg) {
    pthread_mutex_lock(&lock2);
    printf("Thread 2 acquired lock 2\n");
    sleep(1); // 增加死锁发生的可能性
    pthread_mutex_lock(&lock1);
    printf("Thread 2 acquired lock 1\n");
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
    return NULL;
}

int main() 
{
    pthread_t t1, t2;
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    
    pthread_create(&t1, NULL, thread1Func, NULL);
    pthread_create(&t2, NULL, thread2Func, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
    return 0;
}

这个示例中,thread1Func 先锁定 lock1,然后尝试锁定 lock2。同时,thread2Func 先锁定 lock2,然后尝试锁定 lock1。这种交叉锁定容易导致死锁,因为每个线程都在等待对方释放另一把锁。 

3. 读写锁rwlock: 

pthread_rwlock_函数

pthread_rwlock_t rwlock;        用于定义一个读写锁变量

pthread_rwlock_init(&rwlock, NULL);

pthread_rwlock_rdlock(&rwlock);         tryrdlock

pthread_rwlock_wrlock(&rwlock);         trywrlock

pthread_rwlock_unlock(&rwlock);

pthread_rwlock_destroy(&rwlock);

参数与互斥锁类似

以上函数的返回值都是:成功返回0,失败返回错误号

 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享

  • 锁只有一把。以读方式给数据加锁------读锁。以写方式给数据加锁------写锁。
  • 读共享,写独占。
  • 写锁优先级高。
  • 相较于互斥量而言,当读线程多的时候,提高访问效率

读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

代码示例验证写锁优先级高:

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

int counter;
pthread_rwlock_t rwlock;//全局的读写锁

//3个线程不定时写统一全局资源,5个线程不定时读统一全局资源
void *th_write(void *arg)
{
        int t;
        int i = (int)arg;
        while (1) 
        {
                pthread_rwlock_wrlock(&rwlock);//以写模式加锁,写独占
                t = counter;
                usleep(1000);
                printf("===write %d: %lu: counter=%d ++counter=%d\n",i,pthread_self(),t,++counter);
                pthread_rwlock_unlock(&rwlock);
                usleep(10000);
        }
        return NULL;
}

void *th_read(void *arg)
{
        int i = (int)arg;
        while (1) 
        {
                pthread_rwlock_rdlock(&rwlock);//读锁共享
                printf("-----read %d: %lu: %d\n",i,pthread_self(),counter);
                pthread_rwlock_unlock(&rwlock);
                usleep(2000);
        }
        return NULL;
}

int main(void)
{
        int i;
        pthread_t tid[8];

        pthread_rwlock_init(&rwlock,NULL);

        for (i = 0;i < 3;i++)
                pthread_create(&tid[i],NULL,th_write,(void*)i);

        for (i = 0; i < 5; i++)
                pthread_create(&tid[i+3],NULL,th_read,(void*)i);
        
        for (i = 0;i< 8; i++)
                pthread_join(tid[i],NULL);

        pthread_rwlock_destroy(&rwlock);

        return 0;
}

三、条件变量

 条件变量本身不是锁!但它也可以造成线程阻塞,通常与互斥锁配合使用。

主要应用函数:

pthread_cond_init函数                      初始化一个条件变量

pthread_cond_destroy函数               销毁一个条件变量        

pthread_cond_wait函数                    阻塞等待一个条件变量

pthread_cond_timedwait函数           限时等待一个条件变量

pthread_cond_signal函数                 唤醒至少一个阻塞在条件变量上的线程

pthread_cond_broadcast函数          唤醒全部阻塞在条件变量上的线程

参数与互斥锁类似

以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

pthread_cond_t类型      用于定义条件变量

pthread_cond_t cond;

pthread_cond_init函数 

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

参2:attr表条件变量属性,通常为默认值,传NULL即可

也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_cond_wait函数 

pthread_cond_wait(&cond, &mutex);

阻塞等待一个条件变量

作用:

  1. 阻塞等待条件变量cond(参1)满足
  2. 解锁已经掌握的互斥锁【解锁互斥量】(相当于 pthread_mutex_unlock(&mutex)),1 2两步为一个原子操作(一起执行,不可分割)
  3. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁,即重新加锁(相当于pthread_mutex_lock(&mutex);)

1. 当调用pthread_cond_wait函数时,它在阻塞等待条件满足时,会解锁

2. 当条件满足后,重新加锁 

3. pthread_cond_signal()、pthread_cond_broadcast()函数会发送条件满足的信号

4. 通过pthread_cond_timewait()函数来限时等待一个条件变量

条件变量的生产者消费者模型分析: 

条件变量实现生产者消费者代码:

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

void err_thread(int ret,char *str)
{
        if (ret != 0)
        {
                fprintf(stderr,"%s:%d\n",str,strerror(str));
                pthread_exit(NULL);
        }
}

//创建共享数据
struct msg
{
        //struct msg 是一个结构体,包含至少两个字段:
        //num(数据)和 next(指向链表中下一个节点的指针)    
        int num;
        struct msg *next;
};

struct mas *head;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//定义并且初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;//定义并且初始化一个条件变量

void *producer(void *arg)
{
        while(1){       //添加循环多次生产
        struct msg *mp = malloc(sizeof(struct msg));//为新的数据项动态分配内存

        mp->num = rand() % 1000 + 1;//模拟生产一个数据
        printf("---produce %d\n",mp->num);

        pthread_mutex_lock(&mutex);//加锁互斥量
        mp->next = head;//头插法插入链表,写公共区域
        head = mp;
        pthread_mutex_unlock(&mutex); //解锁互斥量

        pthread_cond_signal(&has_data); //唤醒在阻塞条件变量has_data上的线程

        sleep(rand() % 3);
        }
        return NULL;
}

void *consumer(void *arg)
{
        while(1){
        struct msg *mp;
        pthread_mutex_lock(&mutex);//加锁互斥量
        if (head == NULL)
        {
                pthread_cond_wait(&has_data,&mutex);//阻塞等待条件变量,解锁。pthread_connd_wait返回时,重新加锁mutex
        }

        mp = head;
        head = mp->next;

        pthread_mutex_unlock(&mutex);//解锁互斥量
        printf("------consumer:%d\n",mp->num);

        free(mp);
        sleep(rand() % 3);
        }
        return NULL;
}

int main(int argc,char *argv[])
{
        int ret;         
        pthread_t pid,cid;

        srand(time(NULL));

        ret = pthread_create(&pid,NULL,producer,NULL);
        if (ret != 0)
        {
                err_thread(ret,"pthread_create producer error");//生产者
        }
        ret = pthread_create(&cid,NULL,consumer,NULL);
        if (ret != 0)
        {
                err_thread(ret,"pthread_create consumer error");//消费者
        }

        pthread_join(pid,NULL);
        pthread_join(cid,NULL);

        return 0;
}

输出为:

 ---produce 330
------consumer id:139864563013376:330
---produce 839
------consumer id:139864554620672:839
---produce 126
------consumer id:139864554620672:126

多个消费者使用while做:

if (ret != 0)
        {
                err_thread(ret,"pthread_create consumer error");//消费者
        }

复制多份创建消费者的代码,并且在consumer回调函数中将if (head == NULL)修改为while (head == NULL)。若未进行修改,则出现段错误

输出为:

---produce 783
---produce 284
---produce 115
---produce 203
------consumer id:139795136452352:203
------consumer id:139795144845056:115
---produce 199
------consumer id:139795153237760:199
------consumer id:139795153237760:284
------consumer id:139795136452352:783

条件变量signal注意事项:

pthread_cond_signal(): 唤醒阻塞在条件变量上的 (至少)一个线程。

pthread_cond_broadcast(): 唤醒阻塞在条件变量上的 所有线程。

四、信号量

  • 应用于线程、进程间同步。
  • 相当于 初始化值为 N 的互斥量。 N值,表示可以同时访问共享数据区的线程数

sem_函数:

sem_t sem; 定义类型,本质是结构体

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

参数:

        sem: 信号量

        pshared: 0: 用于线程间同步

                        1: 用于进程间同步

        value:N值。(指定同时访问的线程数)

sem_destroy();

sem_wait();一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 -- 就会阻塞。

(对比pthread_mutex_lock)

sem_trywait();

sem_timedwait();

sem_post();一次调用,做一次++ 操作,当信号量的值为 N 时, 再次 ++ 就会阻塞。

(对比 pthread_mutex_unlock)

以上6 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。

信号量的初值,决定了占用信号量的线程的个数

信号量实现的生产者消费者:

分析:

代码示例:

 /*信号量实现 生产者 消费者问题*/
#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<string.h>  
#include<pthread.h>  

#define NUM 5                 
   
int queue[NUM];                                     //全局数组实现环形队列  
sem_t blank_number, product_number;                 //空格子信号量, 产品信号量  
   
void *producer(void *arg)  
{  
     int i = 0;  
     while (1) 
    {  
         sem_wait(&blank_number);                    //生产者将空格子数--,为0则阻塞等待  
         queue[i] = rand() % 1000 + 1;               //生产一个产品  
         printf("----Produce---%d\n", queue[i]);          
         sem_post(&product_number);                  //将产品数++  
   
         i = (i+1) % NUM;                            //借助下标实现环形  
         sleep(rand()%1);  
     }  
}  
   
void *consumer(void *arg)  
{  
     int i = 0;  
     while (1) {  
         sem_wait(&product_number);                  //消费者将产品数--,为0则阻塞等待  
         printf("-Consume---%d\n", queue[i]);  
         queue[i] = 0;                               //消费一个产品   
         sem_post(&blank_number);                    //消费掉以后,将空格子数++  
   
         i = (i+1) % NUM;  
         sleep(rand()%3);  
     }  
}  
   
int main(int argc, char *argv[])  
{  
     sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享--0  
     sem_init(&product_number, 0, 0);                //产品数为0  

     pthread_t pid, cid;  
     pthread_create(&pid, NULL, producer, NULL);  
     pthread_create(&cid, NULL, consumer, NULL);  
   
     pthread_join(pid, NULL);  
     pthread_join(cid, NULL);   
     sem_destroy(&blank_number);  
     sem_destroy(&product_number);  
     return 0;  
 } 

条件变量和信号量实现生产者消费者模型掌握一个即可

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

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

相关文章

新建云仓库

1.GitHub新建云仓库&#xff1a; LICENSE:开源许可证&#xff1b;README.md:仓库说明文件&#xff1b;开源项目&#xff1b;cocoaPodsName.podspec: CocoaPods项目的属性描述文件。 2.Coding新建云仓库&#xff1a; 备注&#xff1a; Coding新建项目&#xff1a;

自动化机器学习流水线:基于Spring Boot与AI机器学习技术的融合探索

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

训练营第三十五天动态规划(基础题part1)

训练营第三十五天动态规划&#xff08;part1&#xff09; 需要用到之前的数据 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509. 斐波那契数 力扣题目链接 题目 斐波那契数 &#xff08;通常用 F(n…

动态规划——斐波那契数列模型:面试题08.01.三步问题

文章目录 题目描述算法原理1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接&#xff1a;面试题08.01.三步问题 如果n是0走法可能是1也可能是0&#xff0c;所以本题范围并不需要考虑直接从1开始即可 因为以3为结尾有直接从0到3的方式&a…

深度学习500问——Chapter08:目标检测(4)

文章目录 8.3 One Stage 目标检测算法 8.3.1 SSD 8.3.2 DSSD 8.3.3 YOLOv1 8.3 One Stage 目标检测算法 我们将对单次目标检测器&#xff08;包括SSD系列和YOLO系列等算法&#xff09;进行综述。我们将分析FPN以理解多尺度特征图如何提高准确率&#xff0c;特别是小目标的检测…

无人机+集群组网:机载自组网电台技术详解

无人机与集群组网的结合为现代通信带来了独特的优势。在集群组网中&#xff0c;每个节点&#xff08;例如无人机&#xff09;都兼具路由器和主机的功能&#xff0c;它们不仅可以运行各种面向用户的应用程序&#xff0c;还可以执行路由协议&#xff0c;根据路由策略和路由表完成…

C#基础之结构体

结构体 文章目录 1、概念2、基本语法3、示例4、结构体的使用5、访问修饰符6、结构体的构造函数思考1 描述矩形信息思考2 职业名字释放了技能思考3 小怪兽思考4 多个小怪兽思考5 奥特曼打小怪兽 1、概念 结构体是一种一定义变量类型 它是数据和函数的集合&#xff0c;可以在结…

谁是存储器市场下一个“宠儿”?

AI浪潮对存储器提出了更高要求&#xff0c;高容量、高性能存储产品重要性正不断凸显&#xff0c;存储产业技术与产能之争也因此愈演愈烈&#xff1a;NAND Flash领域&#xff0c;闪存堆叠层数持续提升&#xff1b;DRAM领域HBM持续扩产&#xff0c;技术不断迭代&#xff0c;同时3…

模仿学习IQ-Learn:Inverse soft-Q Learning for Imitation

论文地址&#xff1a;IQ-Learn: Inverse soft-Q Learning for Imitation 项目地址&#xff1a;IQ-Learn: Inverse soft-Q Learning for Imitation 一、相关概念&#xff1a; 强化学习&#xff08;RL&#xff09;&#xff1a;目标是学习一个最大化指定奖励函数的策略。 模仿…

java-Spring-(MyBatis框架-xml管理)

目录 前置条件 xml与注解比较 1.1 xml定义 1.2 和SQL注解比较 建包准备 插入数据 ​编辑 更新数据 删除数据 查询数据 查看单字段查询 &#x1f3f7;&#x1f4a3;前置条件 创建一个spring boot 初始化的项目 &#x1f3f7;&#x1f4a3;xml与注解比较 1.1 xml定义 …

WebGL开发框架比较

WebGL开发框架提供了一套丰富的工具和API&#xff0c;使得在Web浏览器中创建和操作3D图形变得更加容易。以下是一些流行的WebGL开发框架及其各自的优缺点。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.Three.js 优点&#xff1a…

iText生成PDF文件

导语&#xff1a; 本文基于 iText7 &#xff1a;7.1.16 生成。 官方文档链接&#xff1a;iText 从版本入口可进入到下面页面 一、引言 常见生成PDF文件的有两种方法&#xff0c;一是先生成 word文档&#xff0c;然后将word转换成PDF文件&#xff1b;另一种则是直接生成PDF文件…

javaEE--多线程学习-进程调度

进程调度不明白&#xff1f;看这一篇文章就够了&#xff0c;逻辑衔接严密&#xff0c;文末附有关键面试题&#xff0c;一个海后的小故事让你瞬间明白这里面的弯弯绕绕&#xff01; 目录 1.什么是进程&#xff1f; 2.进程控制块&#xff08;PCB&#xff09; 2.1 一个PCB就是一…

多数据源注解使用

<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version> </dependency> 1.使用mybatis-plus 如何配置多数据源&#xff0c;application-loc…

《QT实用小工具·三十五》基于PathView,Qt/QML做的一个可以无限滚动的日历控件

1、概述 源码放在文章末尾 改项目实现了基于PathView&#xff0c;Qt/QML做的一个可以无限滚动的日历控件&#xff0c;下面是demo演示&#xff1a; 项目部分代码如下所示&#xff1a; import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4Bu…

Qt:学习笔记一

一、工程文件介绍 1.1 main.cpp #include "widget.h" #include <QApplication> // 包含一个应用程序类的头文件 //argc&#xff1a;命令行变量的数量&#xff1b;argv&#xff1a;命令行变量的数组 int main(int argc, char *argv[]) {//a应用程序对象&…

深度学习突破:LLaMA-MoE模型的高效训练策略

在人工智能领域&#xff0c;大模型&#xff08;LLM&#xff09;的崛起带来了前所未有的进步&#xff0c;但随之而来的是巨大的计算资源需求。为了解决这一问题&#xff0c;Mixture-of-Expert&#xff08;MoE&#xff09;模型架构应运而生&#xff0c;而LLaMA-MoE正是这一架构下…

屌爆了,国产智能体的爆发性增长:一周数据揭秘

前言 自从4.19接入国内智能体 探索国产智能体&#xff1a;从GPTs使用热潮到本土模型的崛起 截止到今天(4.26)&#xff0c;仅仅一周时间&#xff0c;数据太让我意外了&#xff0c;这篇文章就来总结一下&#xff01; 这一次&#xff0c;我为国产智能体崛起&#xff0c;代言!!! 惊…

释放Stable Diffusion 无限可能

最近在整理大语言模型的系列内容&#xff0c;Stable Diffusion 是我下一篇博客的主题。关注 Stable Diffusion&#xff0c;是因为它是目前最受欢迎和影响力最大的多模态生成模型之一。Stable Diffusion 于 2022 年 8 月发布&#xff0c;主要用于根据文本的描述产生详细图像&…

《欢乐钓鱼大师》攻略,钓友入坑必备!

欢迎来到《欢乐钓鱼大师》&#xff01;在这个游戏里&#xff0c;你可以尽情享受垂钓的乐趣&#xff0c;通过不断更换和升级高阶鱼竿&#xff0c;轻松地钓到各种稀有鱼类。因为许多玩家在挑战关卡时遇到了一些困难&#xff0c;所以今天我给大家带来了《欢乐钓鱼大师攻略指南》&a…