12.线程同步
12.线程同步
- 1. 为什么需要线程同步
- 2. 互斥锁
- 2.1 互斥锁初始化
- 2.1.1 PTHREAD_MUTEX_INITIALIZER 宏初始化
- 2.1.2 使用函数初始化
- 2.2 加锁和解锁
- 2.3 pthread_mutex_trylock()
- 2.4 销毁互斥锁
- 2.5 互斥锁死锁
- 2.6 互斥锁的属性
- 3. 条件变量
- 3.1 条件变量初始化
- 3.2 通知和等待条件变量
- 3.3 条件变量的判断条件
- 4. 自旋锁
- 4.1 初始化
- 4.2 加锁和解锁
- 5. 读写锁
- 5.1 初始化
- 5.2 加锁和解锁
- 5.3 属性
- 6. 线程安全
- 6.1 线程栈
- 6.2 可重入函数
- 6.3 线程安全函数
- 6.4 一次性初始化
- 6.5 线程特有数据
- 6.5.1 pthread_key_create()
- 6.5.2 pthread_setspecific()
- 6.5.3 pthread_getspecific()
- 6.5.4 pthread_key_delete()
- 6.6 线程局部存储
1. 为什么需要线程同步
线程的主要优势在于资源的共享性,但是多个线程并发访问共享数据所导致的数据不一致问题。**线程同步是为了对共享资源的访问进行保护。保护的目的是为了解决数据一致性的问题。**只有当多个线程都可以修改或获取这个变量的时候,就需要进行同步。出现数据一致性问题其本质在于进程中的多个线程对共享资源的并发访问。
线程同步就是同一时间只允许一个线程访问该变量,防止出现并发访问。下面介绍的就是 Linux 系统中用于实现同步的机制。
2. 互斥锁
在访问共享资源之前对互斥锁进行上锁,访问完后进行解锁。上锁后,其他线程就不允许访问这一部分共享资源。如果解锁时有一个以上的线程阻塞,那么这些被阻塞的线程就会被唤醒,都会尝试对互斥锁进行加锁,加锁成功后,其他线程进行阻塞等待。
互斥锁用 pthread_mutex_t
类型表示,使用之前,需要先进行初始化。
2.1 互斥锁初始化
2.1.1 PTHREAD_MUTEX_INITIALIZER 宏初始化
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
这种方法只适用在定义的时候初始化,对于其他情况就不行,譬如先定义然后再进行初始化,或者在堆中动态分配的互斥锁。
2.1.2 使用函数初始化
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
attr 指向的对象是互斥锁的属性,如果设置为 NULL,表示属性为默认值。函数成功调用就返回 0,失败返回非 0 的错误码。
2.2 加锁和解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
进行上锁时,如果处于未上锁状态,就调用成功,函数立即返回;如果互斥锁已经被锁定,那么函数会阻塞,直到该互斥锁解锁,然后锁定并返回。
进行解锁时,不能对未处于锁定状态的互斥锁进行解锁,也不能解锁其它线程锁定的互斥锁。如果有多个线程处于阻塞状态等待被解锁,当互斥锁被当前锁定它的线程解锁后,这些等待的线程都能够进行上锁,但是无法确定具体是哪一个线程。
2.3 pthread_mutex_trylock()
如果在互斥锁被锁时,不希望线程被阻塞,可以使用该函数。该函数尝试对互斥锁进行加锁,如果互斥锁处于未锁状态,那么函数会上锁并立马返回。如果已经被锁,就会加锁失败,但不会被阻塞,而是返回错误码 EBUSY
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
2.4 销毁互斥锁
当不需要互斥锁时,应该将其销毁。
#include <pthread.h>
int pthread_destroy(pthread_mutex_t *mutex);
注意:不能销毁还没有解锁的互斥锁,也不能销毁还未初始化的互斥锁。销毁后,就不能再进行上锁或解锁,要重新进行初始化才能够使用
2.5 互斥锁死锁
如果一个线程试图对同一个互斥锁加锁两次,该线程就会陷入死锁状态,一直被阻塞永远出不来,除此之外,还有其它很多种方式也能产生死锁。譬如,程序中使用一个以上的互斥锁,如果允许一个线程一直占有第一个互斥锁,并且在试图锁住第二个互斥锁时处于阻塞状态,但是拥有第二个互斥锁的线程也在试图锁住第一个互斥锁。因为两个线程都在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,会被一直阻塞,于是就产生了死锁。
为了避免上诉的情况,就需要定义层级关系,这里就不介绍了。
2.6 互斥锁的属性
定义 pthread_mutexattr_t 对象之后,需要进行初始化,不再使用时,需要将其销毁.
#include <pthread.h>
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
属性有很多种,这里只讨论类型属性。
- PTHREAD_MUTEX_NORMAL: 标准的互斥锁类型,不做任何的错误检查或死锁检测。如果线程试图对已经由自己锁定的互斥锁再次进行加锁,则发生死锁;互斥锁处于未锁定状态,或者已由其它线程锁定,对其解锁会导致不确定性。
- PTHREAD_MUTEX_ERRORCHECK: 会提供错误检查。。譬如这三种情况都会导致返回错误:线程试图对已经由自己锁定的互斥锁再次进行加锁(同一线程对同一互斥锁加锁两次),返回错误;线程对由其它线程锁定的互斥锁进行解锁,返回错误;线程对处于未锁定状态的互斥锁进行解锁,返回错误。这类互斥锁运行起来比较慢,因为它需要做错误检查,不过可将其作为调试工具,以发现程序哪里违反了互斥锁使用的基本原则。
- PTHREAD_MUTEX_RECURSIVE: 此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁,但是如果解锁次数不等于加速次数,则是不会释放锁的;所以,如果对一个递归互斥锁加锁两次,然后解锁一次,那么这个互斥锁依然处于锁定状态,对它再次进行解锁之前不会释放该锁。
- PTHREAD_MUTEX_DEFAULT : 此类 互 斥锁提供默认的行为和特性。使 用 宏PTHREAD_MUTEX_INITIALIZER 初始化的互斥锁,或 者调用参数 arg 为 NULL 的 pthread_mutexattr_init() 函数所创建的互斥锁,都属于此类型。此类锁意在为互斥锁的实现保留最大灵活性
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
// 调用成功,将互斥锁类型属性保存在参数 type 所指向的内存中,通过它返回出来
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
// attr 指向的对象类型属性设置为 type 指定的类型。
3. 条件变量
条件变量用于自动阻塞线程,直到某个特定事件发生或某个条件满足为止。通常情况下,条件变量和互斥锁一起搭配使用的,使用条件变量主要包括两个动作:一个线程等待某个条件满足而被阻塞;另一个线程中,条件满足时发出信息。也就是常说的生产消费模型。
条件变量通常搭配互斥锁来使用,是因为条件的检测是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的,线程在改变条件状态之前必须首先锁住互斥锁,不然就会引发线程不安全问题。
3.1 条件变量初始化
pthread_cond_t cond=PTHREAD_COND_INITIALIZER; // 使用宏进行初始化#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *cond,const pthread_condaddr_t *attr);
注意:
- 对已经初始化的条件变量再进行初始化,将可能导致未定义行为;
- 对没有进行初始化的条件变量进行销毁,也可能导致未定义行为;
- 对某个条件变量而言,仅当没有任何线程等待它时,将其销毁才是最安全的
3.2 通知和等待条件变量
条件变量的主要操作就是发送信号和等待。发送信号操作即是通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变,这些处于等待状态的线程收到通知后便会被唤醒,唤醒之后再检查条件是否满足。等待操作是指在收到一个通知之前一直处于阻塞状态。
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
前两个函数都是向指定的条件变量发送信号,第一个是唤醒所有线程,第二个只需要至少有一个线程被唤醒。
等待函数是一个阻塞式函数,当判断程序中条件变量不满足时,调用该函数将线程设置为阻塞状态。在函数内部会对 mutex 进行操作,在调用函数时,会把互斥锁传递给函数,函数会自动把调用线程放到等待条件的线程列表上,然后将互斥锁解锁;当该函数返回时,会再次锁住互斥锁。就算是唤醒全部线程,互斥锁也只能被一个线程锁住,其它线程进入阻塞状态。
条件变量并不保存信息,只是传递应用程序状态信息的一种通讯机制。
3.3 条件变量的判断条件
在判断条件时,使用的是 while() 循环,而不是 if,因为当函数返回时,并不能确定条件的成立与否,要立即重新检查判断条件,如果条件不满足,就继续休眠等待。因为如果有多个线程在等待条件变量,任何线程都有可能率先醒来获取互斥锁,就可能会对条件变量进行修改,改变判断条件的状态
4. 自旋锁
自旋锁和互斥锁相似,事实上,互斥锁是由自旋锁实现的。
如果在获取自旋锁时处于未锁定状态,就立即获得锁,如果已经上锁,就在原地自旋,直到该自旋锁的持有者释放了锁。由此可知,自旋锁一直占用 CPU,如果不能短时间内获得锁,就会使 CPU 效率降低。
自旋锁在用户态中使用的比较少,通常使用在内核代码中。因为自旋锁可以在中断服务函数中使用,而互斥锁不行,在执行中断服务函数时要求不能休眠,不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了 CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就导致了死锁。
4.1 初始化
#include <pthread.h>
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
pshared 表示自旋锁的进程共享属性,PTHREAD_PROCESS_SHARED
共享自旋锁,可以在多个进程中的线程之间共享;PTHREAD_PROCESS_PRIVATE
私有自旋锁,只有本进程内的线程才能够使用该自旋锁。
4.2 加锁和解锁
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
// 一直自旋,直到获取到
int pthread_spin_trylock(pthread_spinlock_t *lock);
// 如果未能获取,就立即返回错误,错误码 EBUSY
int pthread_spin_unlock(pthread_spinlock_t *lock);
5. 读写锁
读写锁有三种状态,读加锁、写加锁和不加锁状态,一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。
当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作的线程都会被阻塞;
处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功,但是以写模式的线程会被阻塞,直到所有持有读模式的线程释放它们的锁为止。
5.1 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
5.2 加锁和解锁
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
当读写锁处于写模式加锁状态时,其它线程调用 pthread_rwlock_rdlock()或 pthread_rwlock_wrlock()函数均会获取锁失败,从而陷入阻塞等待状态;当读写锁处于读模式加锁状态时,其它线程调用 pthread_rwlock_rdlock() 函数可以成功获取到锁,如果调 pthread_rwlock_wrlock()函数则不能获取到锁,从而陷入阻塞等待状态。
如果线程不希望被阻塞,可以调用 pthread_rwlock_tryrdlock() 和 pthread_rwlock_trywrlock() 来尝试加锁,如果不可以获取锁时。这两个函数都会立马返回错误,错误码为 EBUSY。
5.3 属性
#include <pthread.h>
// 属性的定义和销毁函数
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
// 属性的获取和设置函数
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
读写锁只有一个共享属性。get 时将属性存放在 pshared 所指向的内存中,set 时设置为内存中指定的指,可取值如下:
- PTHREAD_PROCESS_SHARED: 共享读写锁。该读写锁可以在多个进程中的线程之间共享;
- PTHREAD_PROCESS_PRIVATE: 私有读写锁。只有本进程内的线程才能够使用该读写锁,这是读写锁共享属性的默认值。
6. 线程安全
6.1 线程栈
进程中创建的每个线程都有自己的栈地址空间,将其称为线程栈。每个线程运行过程中所定义的局部变量都是分配在自己的线程栈中,不会互相干扰。
6.2 可重入函数
可重入函数就是被同一个进程的多个不同执行流并行调用,并且每次调用都能产生正确的结果,就叫做可重入函数。
6.3 线程安全函数
一个函数被多个线程(其实也是多个执行流,但是不包括由信号处理函数所产生的执行流) 同时调用
时,它总会一直产生正确的结果,把这样的函数称为线程安全函数。 线程安全函数包括可重入函数, 也就是说可重入函数一定是线程安全函数,但线程安全函数不一定是可重入函数。
6.4 一次性初始化
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
保证init_routine
函数只执行一次,但是不能保证在哪个线程中执行,是由内核调度决定的。
在调用 pthread_once() 函数之前,需要定义一个 pthread_once_t 类型的静态变量,通常使用宏pthread_once_t once_control = PTHREAD_ONCE_INIT;
进行初始化。
如果参数 once_control 指向的 pthread_once_t 类型变量,其初值不是 PTHREAD_ONCE_INIT,pthread_once() 的行为将是不正常的;
如果在一个线程调用 pthread_once() 时,另外一个线程也调用了,则该线程将会被阻塞等待, 直到第一个完成初始化后返回。换言之,当调用 pthread_once 成功返回时,调用总是能够保证所有的状态已经初始化完成了。
6.5 线程特有数据
也称为线程私有数据,就是为每个调用线程分别维护一份变量的副本,每个线程通过特有数据键(key)访问时,这个特有数据键都会获取到本线程绑定的变量副本,这样就可以避免变量成为多个线程间的共享数据。
6.5.1 pthread_key_create()
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
在为线程分配私有数据区之前,需要调用该函数创建一个特有数据键,并且只需要在首个调用的线程中创建一次即可。通过 key 所指向的缓冲区返回给调用者,destructor 是一个自定义析构函数,通常用于释放与特有数据键关联的线程私有数据区占用的内存空间,当使用线程特有数据的线程终止时,析构函数会自动调用。
6.5.2 pthread_setspecific()
#include <pthread.h>
int pthread_setspecific(pthread_key_t key, const void *value);
创建特有数据键之后,需要为调用线程分配私有数据缓冲区,每个调用线程分配一次,且只会在线程初次调用此函数时分配。首先保存指向线程私有数据缓冲区的指针,并将其与特有数据键以及当前调用线程关联起来。
value 就是一块由调用者分配的内存,作为线程的私有数据缓冲区,当线程终止时,会自动调用参数 key 指定的特有数据键对应的析构函数来释放这一块动态申请的内存空间。
6.5.3 pthread_getspecific()
#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
可以用来获取调用线程的私有数据区。返回值是一个指针,指向这一块缓冲区。如果没有进行关联,返回值为 NULL,所以如果是初次调用该函数,则必须为该线程分配私有数据缓冲区。
6.5.4 pthread_key_delete()
#include <pthread.h>
int pthread_key_delete(pthread_key_t key);
删除特有数据键,但是不会检查当前是否有线程正在使用该键所关联的线程私有数据缓冲区,所以不会触发析构函数,也就不会释放资源。所以,调用该函数前,要保证所有线程已经释放了私有数据区(显示调用析构函数或线程终止),key 指定的特有数据键将不再使用。
6.6 线程局部存储
线程局部存储在定义全局或静态变量时,使用 __thread 修饰变量,此时,每个线程都会拥有一份对该变量的拷贝,线程局部存储中的变量将一直存在,直到线程终止,届时会自动释放这一存储。比如```static __thread char c;``线程布局存储也是一个变量,可以对其取地址操作或赋初值。
相关文章:

12.线程同步
12.线程同步 1. 为什么需要线程同步2. 互斥锁2.1 互斥锁初始化2.1.1 PTHREAD_MUTEX_INITIALIZER 宏初始化2.1.2 使用函数初始化 2.2 加锁和解锁2.3 pthread_mutex_trylock()2.4 销毁互斥锁2.5 互斥锁死锁2.6 互斥锁的属性 3. 条件变量3.1 条件变量初始化3.2 通知和等待条件变量…...

开发安全之:System Information Leak: External
Overview 在调用 error_reporting() 过程中,程序可能会显示系统数据或调试信息。由 error_reporting() 揭示的信息有助于攻击者制定攻击计划。 Details 当系统数据或调试信息通过套接字或网络连接使程序流向远程机器时,就会发生外部信息泄露。 示例 1…...

burp靶场--文件上传
burp靶场–文件上传 https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-web-shell-upload 1.文件上传 1、原理:文件上传漏洞是指Web服务器允许用户将文件上传到其文件系统,而不充分验证文件的名称、类型、…...

mac 中vscode设置root启动
1. 找到你的vscode app,点击鼠标右键------->选项----->在访达中显示 2. 终端中输入以下命令,不要点回车,不要点回车,输入一个空格 sudo chflags uchg 3. 然后将你的程序拖到终端,会自动…...

【MySQL数据库专项 一】一个例子讲清楚数据库三范式
好的,让我们以学校数据库中的一个表为例来说明第一范式(1NF)、第二范式(2NF)和第三范式(3NF)的概念。 什么是数据库三范式 数据库的范式(Normalization)是一组关于数据…...

【笔记】关于期刊
什么是统计源期刊 统计源期刊,全称为“中国科技论文统计源期刊”,也称作中国科技核心期刊,是由中国科技信息研究所(ISTIC)受国家科技部委托,选定的一系列在中国出版的高质量自然科学类学术期刊。这些期刊是…...

SpringMVC-.xml的配置
文章目录 一、对pom.xml的配置二、对web.xml1.第一种方式2. 第二种方式 三、对SpringMVC.xml的配置 一、对pom.xml的配置 <!-- 打包成war包--><packaging>war</packaging> <dependencies><!-- SpringMVC--><dependency><gro…...

Java找二叉树的公共祖先
描述: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节…...

《Linux高性能服务器编程》笔记03
Linux高性能服务器编程 本文是读书笔记,如有侵权,请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第07章 Linux服务器程序规范7.1日志7.2用…...

Java毕业设计-基于ssm的网上求职招聘管理系统-第85期
获取源码资料,请移步从戎源码网:从戎源码网_专业的计算机毕业设计网站 项目介绍 基于ssm的网上求职招聘管理系统:前端 jsp、jquery,后端 springmvc、spring、mybatis,角色分为管理员、招聘人员、用户;集成…...

UDP和TCP
UDP协议是一种不可靠的、面向无连接的协议。在通信过程中,它并不像TCP那样需要先建立一个连接,只要(目的地址,端口号,源地址,端口号)确定了,就可以直接发送信息报文,并且…...

【C++】vector容器接口要点的补充
接口缩容 在VS编译器的模式下,类似于erase和insert接口的函数通常会进行缩容,因此,insert和erase行参中的迭代器可能会失效。下图中以erase为例: 代码如下: #include <iostream> #include <vector> #inclu…...

electron-vite中的ipc通信
1. 概述 再electron中,进程间的通信通过ipcMain和ipcRenderer模块,这些通道是任意和双向的 1.1. 什么是上下文隔离进程 ipc通道是通过预加载脚本绑定到window对象的electron对象属性上的 2. 通信方式 2.1. ipcMain(也就是渲染进程向主进…...

探秘网络爬虫的基本原理与实例应用
1. 基本原理 网络爬虫是一种用于自动化获取互联网信息的程序,其基本原理包括URL获取、HTTP请求、HTML解析、数据提取和数据存储等步骤。 URL获取: 确定需要访问的目标网页,通过人工指定、站点地图或之前的抓取结果获取URL。 HTTP请求&#…...

音视频编解码学习记录
目录 学习资料个人git仓库 文章 学习资料 个人git仓库 标准,资料,笔记: https://gitee.com/fedorayang/video_and_audio_codec.git 文章 理解低延迟视频编码的正确姿势: https://cloud.tencent.com/developer/article/1358721...

零基础小白刚刚入门Python的注意点总结~
文章目录 一、注意你的Python版本1.print()函数2.raw_input()与input()3.比较符号,使用!替换<>4.repr函数5.exec()函数 二、新手常遇到的问题1、如何写多行程序?2、如何执行.py文件?3、and,or,not4、True和False…...

从 Context 看 Go 设计模式:接口、封装和并发控制
文章目录 Context 的基本结构Context 的实现和传递机制为什么 Context 不直接传递指针案例:DataStore结论 在 Go 语言中, context 包是并发编程的核心,用于传递取消信号和请求范围的值。但其传值机制,特别是为什么不通过指针传递…...

微信小程序字体大小
微信小程序中可以使用以下CSS样式来设置字体大小: font-size: 12px; // 设置字体大小为12像素在小程序中,可以直接在WXML文件和WXSS文件中使用这个样式。...

L1-062 幸运彩票(Java)
彩票的号码有 6 位数字,若一张彩票的前 3 位上的数之和等于后 3 位上的数之和,则称这张彩票是幸运的。本题就请你判断给定的彩票是不是幸运的。 输入格式: 输入在第一行中给出一个正整数 N(≤ 100)。随后 N 行&#…...

【计算机网络】2、传输介质、通信方向、通信方式、交换方式、IP地址表示、子网划分
文章目录 传输介质双绞线无屏蔽双绞线UTP屏蔽双绞线STP 网线光纤多模光纤MMF单模光纤SMF 无线信道无线电波红外光波 通信方向单工半双工全双工 通信方式异步传输同步传输串行传输并行传输 交换方式电路交换报文交换分组交换 IP地址表示IP地址的定义IP地址的分类无分类编址特殊I…...

【Linux 内核源码分析】堆内存管理
堆 堆是一种动态分配内存的数据结构,用于存储和管理动态分配的对象。它是一块连续的内存空间,用于存储程序运行时动态申请的内存。 堆可以被看作是一个由各个内存块组成的堆栈,其中每个内存块都有一个地址指针,指向下一个内存块…...

Qt 5.15.2 (MSVC 2019)编译 QWT 6.2.0 : 编译MingW或MSVC遇到的坑
MingW下编译QWt 6.2.0 下载qwt最新版本,用git工具 git clone下载源码 git clone https://git.code.sf.net/p/qwt/git qwt-git 或者使用我下载的 qwt 2.6.0 链接:https://pan.baidu.com/s/1KZI-L10N90TJobeqqPYBqw?pwdpq1o 提取码:pq1o 下载…...

模具制造企业ERP系统有哪些?企业怎么选型适配的软件
模具的生产管理过程比较繁琐,涵盖接单报价、车间排期、班组负荷评估、库存盘点、材料采购、供应商选择、工艺流转、品质检验等诸多环节。 有些采用传统管理手段的模具制造企业存在各业务数据传递不畅、信息滞后、不能及时掌握订单和车间生产情况,难以对…...

管理信息系统知识点复习
目录 一、名词解释题1.企业资源规划(ERP)2.面向对象方法:3.电子健康:4.供应链5.数据挖掘6.“自上而下”的开发策略:7.业务流程重组8.面向对象:9.决策支持系统10.聚类11.集成开发环境:12.供应商协同13.数据仓库14.深度学…...

【Bug】.net6 cap总线+rabbitmq延时消息收不到
文章目录 问题问题代码原因解决处理Bug的具体步骤 问题 我有两个服务一个叫05一个叫15 然后用的cap总线rabbitmq 05消息队列发了一条延时消息,到时间了05服务的订阅者能收到 15服务订阅同一个消息的没收到(cap的cashboard)(手动requeue05和15都能收到&a…...

在 Python 中检查一个数字是否是同构数
更多资料获取 📚 个人网站:ipengtao.com 同构数,又称为自守数或自同构数,是一类特殊的数字,它们具有一种有趣的性质:将其平方后的数字,可以通过某种方式重新排列得到原来的数字。本文将详细介绍…...

【 Qt 快速上手】-①- Qt 背景介绍与发展前景
文章目录 1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt的应用场景1.7 Qt的成功案例1.8 Qt的发展前景及就业分析行业发展方向就业方面的发展前景 1.1 什么是 Qt Qt 是一个跨平台的 C 图形用户界面应用程序框架。它为应用程序开发者提供了建立…...

Kafka-消费者-KafkaConsumer分析-PartitionAssignor
Leader消费者在收到JoinGroupResponse后,会按照其中指定的分区分配策略进行分区分配,每个分区分配策略就是一个PartitionAssignor接口的实现。图是PartitionAssignor的继承结构及其中的组件。 PartitionAssignor接口中定义了Assignment和Subscription两个…...

【办公软件篇】软件启动器Lucy打造自己的工具箱
【办公软件篇】软件启动器Lucy打造自己的工具箱 自从Rolan改为订阅制后就弃用了,本次推荐一款快速启动器Lucy,不联网,不写注册表,体积小,绿色免安装,免费无广告,风格简洁但不简单,目…...

C#MQTT编程08--MQTT服务器和客户端(cmd版)
1、前言 前面完成了winform版,wpf版,为什么要搞个cmd版,因为前面介绍了mqtt的报文结构,重点分析了【连接报文】,【订阅报文】,【发布报文】,这节就要就看看实际报文是怎么组装的,这…...