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

一、同步概念

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

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

数据混乱的原因:

  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.mzph.cn/news/829052.shtml

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

相关文章

算法模版自用(杂)

文章目录 算法库函数next_permutation(start,end) prev_permutation(start,end) (全排列函数)nth_element &#xff08;求第k小值&#xff09;next(it,num),prev(it,num)min_element(begin(),end()),max_element(begiin(),end()) (取最小值最大值) _int128的输入输出STLlist 数…

内容互动性的提升策略:Kompas.ai的智能工具

在数字营销的新时代&#xff0c;内容的互动性已成为提升用户参与度和品牌忠诚度的关键因素。互动性内容不仅能够吸引用户的注意力&#xff0c;还能够促进用户与品牌的沟通和交流&#xff0c;从而加深用户对品牌的理解和认同。本文将分析互动性内容在提升用户参与度中的作用及其…

基于DEAP数据集的四种机器学习方法的情绪分类

在机器学习领域&#xff0c;KNN&#xff08;K-Nearest Neighbors&#xff09;、SVM&#xff08;Support Vector Machine&#xff09;、决策树&#xff08;Decision Tree&#xff09;和随机森林&#xff08;Random Forest&#xff09;是常见且广泛应用的算法。 介绍 1. KNN&am…

【Java】从0实现一个消息队列中间件

从0实现一个消息队列中间件 什么是消息队列需求分析核心概念核心API交换机类型持久化网络通信网络通信API 消息应答 模块划分项目创建创建核心类创建Exchange创建MSGQueue创建Binding创建Message 数据库设计配置sqlite实现创建表和数据库基本操作 实现DataBaseManager创建DataB…

按现价和不变价计算与公布的统计指标主要有哪些

在经济统计和分析工作中 , 有些指标可以直接用实物量表示 , 如粮食和工业品产量等&#xff1b;而有些指标则是用价值量表示的 , 如全国居民人均可支配收入、社会消费品零售总额、商品房销售额等。在计算价值量指标时&#xff0c;一般均要考虑采用什么价格来计算。统计上常用的价…

设计模式(三):抽象工厂模式

设计模式&#xff08;三&#xff09;&#xff1a;抽象工厂模式 1. 抽象工厂模式的介绍2. 抽象工厂模式的类图3. 抽象工厂模式的实现3.1 创建摩托车的接口3.2 创建摩托车的具体实现3.3 创建汽车的接口3.4 创建汽车的具体产品3.5 创建抽象工厂3.6 创建具体工厂3.7 创建工厂生成器…

苹果一次性开源了8个大模型! 包含模型权重、训练日志和设置,OpenELM全面开源

不以开放性著称的苹果居然同时开源了大模型的权重、训练和评估框架&#xff0c;涵盖训练日志、多个保存点和预训练设置。同时升级计算机视觉工具包 CVNets 为 CoreNet&#xff01;支持 OpenELM&#xff01; ▲图1.由Stable Diffusion3生成。 OpenELM是Apple苹果公司最新推出的…

律师口才训练技巧课程介绍?

律师口才训练技巧课程介绍 一、课程背景与目标 律师口才作为法律职业的核心能力之一&#xff0c;对于律师在**辩论、法律咨询、谈判协商等场合的表现具有至关重要的作用。然而&#xff0c;许多律师在口才方面存在不足&#xff0c;难以充分发挥自己的专业能力。因此&#xff0c;…

底层逻辑(1) 是非对错

底层逻辑(1) 是非对错 关于本书 这本书的副标题叫做&#xff1a;看清这个世界的底牌。让我想起电影《教父》中的一句名言&#xff1a;花半秒钟就看透事物本质的人&#xff0c;和花一辈子都看不清事物本质的人&#xff0c;注定是截然不同的命运。 如果你看过梅多丝的《系统之美…

“AI 程序员入职系列”第二弹:如何利用通义灵码光速改写项目编程语言?

通义灵码入职阿里云云原生团队后&#xff0c;已经展示过 Ta 生成单元测试和自动生成代码的强大实力。今天&#xff0c;阿里云后端工程师云徊将从项目开发的实际需求出发&#xff0c;演示通义灵码在开发工作中可提供的帮助。 通义灵码在 Git 开发项目中起到了哪些作用&#xff…

WildCard开通GitHub Copilot

更多AI内容请关注我的专栏&#xff1a;《体验AI》 期待您的点赞&#x1f44d;收藏⭐评论✍ WildCard开通GitHub Copilot GitHub Copilot 简介主要功能工作原理 开通过程1、注册Github账号2、准备一张信用卡或虚拟卡3、进入github copilot页4、选择试用5、选择支付方式6、填写卡…

为什么单片机控制电机需要加电机驱动

通常很多地方只是单纯的单片机MCU没有对电机的驱动能力&#xff0c;或者是介绍关于电机驱动的作用&#xff0c;如&#xff1a; 提高电机的效率和精度。驱动器采用先进的电子技术和控制算法&#xff0c;能够精准控制电机的参数和运行状态&#xff0c;提高了电机的效率和精度。拓…

【Hello算法】 > 第 3 关 >栈与队列

数据结构 之 数组与链表 1 栈 / 栈的常见操作、实现、应用2 队列 /队列的常见操作、实现、应用3 双向队列4 Tips ———————————————————————————————————————————————————————————- ————————————————…

Hybrid Homomorphic Encryption:SE + HE

参考文献&#xff1a; [NLV11] Naehrig M, Lauter K, Vaikuntanathan V. Can homomorphic encryption be practical?[C]//Proceedings of the 3rd ACM workshop on Cloud computing security workshop. 2011: 113-124.[MJS16] Maux P, Journault A, Standaert F X, et al. To…

STM32应用开发教程进阶--UART串口重定向(printf)

实现目标 1、掌握STM32 HAL库的串口重定向 2、具体目标&#xff1a;1、实现printf “打印”各种常用的类型的数据变量 一、串口“打印” UART串口通信协议是我们常用的通信协议&#xff08;UART、I2C、SPI等&#xff09;之一&#xff0c;全称叫做通用异步收发传输器&#xf…

Druid高性能数据库连接池?SpringBoot整合MyBatis整合SpringMVC整合Druid

文章目录 Druid高性能数据库连接池&#xff1f;SpringBoot整合MyBatis整合SpringMVC整合Druid异常记录spring-boot-starter-parent作用Druid介绍什么是数据库连接池&#xff1f;为什么选择Druid数据库连接池整合SpringBoot,MyBatis,SpringMVC,Druid到Maven项目的真个流程pom文件…

OSPF域间路由防环原则

1.域间路由防环原则 ①原则一 1&#xff09;为了避免区域间的环路&#xff0c;OSPF规定不同区域间的路由交互只能通过ABR实现。 2&#xff09;ABR是连接到骨干区域的&#xff0c;所以在区域设计上规定&#xff0c;所有非骨干区域都要连接到骨干区域。区 域间的通讯需要通…

C语言进阶:进阶指针(下)

一、 函数指针数组 我们都知道 数组是一个存放相同类型数据的存储空间 那我们已经学习了指针数组 那么函数有没有对应的指针数组呢&#xff1f; 如果有那应该怎么定义呢&#xff1f; 1. 函数指针数组的定义 我们说 函数指针数组的定义 应该遵循以下格式 int (*p[10])(); 首…

SpringBoot Aop使用篇

Getting Started SpringBoot AOP的实践 AOP相关的概念&#xff1a; Aspect&#xff08;切面&#xff09;&#xff1a; Aspect 声明类似于 Java 中的类声明&#xff0c;在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。就是抽离出来的逻辑类&#xff0c;比如日志、权限…

C++及QT的线程学习

目录 一. 线程学习 二. 学习线程当中&#xff0c;得到的未知。 1. 了解以下MainWindow和main的关系 2. []()匿名函数 有函数体&#xff0c;没有函数名. 3. join和detach都是用来管理线程的生命周期的&#xff0c;它们的区别在于线程结束和资源的回收。 4. operator()() 仿…