进程和线程的区别
进程是资源分配和调度的基本单位。
线程是程序执行的最小单位,线程是进程的子任务,是进程内的执行单元。 一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存。
资源开销:
- 进程:由于每个进程都有独立的内存空间,创建和销毁进程的开销较大。进程间切换需要保存和恢复整个进程的状态,因此上下文切换的开销较高。
- 线程:线程共享相同的内存空间,创建和销毁线程的开销较小。线程间切换只需要保存和恢复少量的线程上下文,因此上下文切换的开销较小。
通信与同步: - 进程:由于进程间相互隔离,进程之间的通信需要使用一些特殊机制,如管道、消息队列、共享内存等。
- 线程:由于线程共享相同的内存空间,它们之间可以直接访问共享数据,线程间通信更加方便。
安全性: - 进程:由于进程间相互隔离,一个进程的崩溃不会直接影响其他进程的稳定性。
- 线程:由于线程共享相同的内存空间,一个线程的错误可能会影响整个进程的稳定性。
进程间有哪些通信方式
1.管道:是一种半双工的通信方式,数据只能单向流动而且只能在具有父子进程关系的进程间使用。
2.命名管道: 类似管道,也是半双工的通信方式,但是它允许在不相关的进程间通信。
3.**消息队列:**允许进程发送和接收消息,而消息队列是消息的链表,可以设置消息优先级。
4.信号:用于发送通知到进程,告知其发生了某种事件或条件。
5.信号量:是一个计数器,可以用来控制多个进程对共享资源的访问,常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此主要作为进程间以及同一进程内不同线程之间的同步手段。
6.共享内存:就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的进程通信方式,
7.Socket套接字:是支持TCP/IP 的网络通信的基本操作单元,主要用于在客户端和服务器之间通过网络进行通信。
8.互斥锁:一种信号量,用于保护共享数据结构,防止多个进程同时访问。
9.条件变量:与互斥锁配合使用,用于进程间的同步,等待某些条件成立。
进程怎么启动,使用什么命令
在Linux系统中,启动进程的方法多种多样,以下是一些常用的命令:
- nohup命令:用于在后台运行命令,即使终端关闭也不会终止进程。同时,该命令将输出重定向到名为nohup.out的文件中。使用格式为nohup command &。
- &符号:在命令末尾加上&,可以将命令放到后台运行,不占用当前终端。例如command &。
- bg命令:用于将一个已暂停的进程放到后台继续运行。使用格式为bg %jobid,其中jobid是作业的ID。
- screen命令:可以创建一个虚拟终端,将进程放到虚拟终端中运行。使用screen -S session_name创建一个新的会话,并在其中执行命令。使用Ctrl+A然后按D可以分离终端,让进程在后台运行。
- systemctl命令:在基于Systemd的Linux发行版中,使用systemctl start service_name命令启动系统服务。
- service命令:在一些基于SysV Init的Linux发行版中,使用service service_name start命令启动系统服务。
线程间如何通信
线程同步机制是指在多线程编程中,为了保证线程之间的互不干扰,而采用的一种机制。常见的线程同步机制有以下几种:
1.互斥锁:互斥锁是最常见的线程同步机制。它允许只有一个线程同时访问被保护的临界区(共享资源)
2.条件变量:条件变量用于线程间通信,允许一个线程等待某个条件满足,而其他线程可以发出信号通知等待线程。通常与互斥锁一起使用。
3.读写锁: 读写锁允许多个线程同时读取共享资源,但只允许一个线程写入资源。
4.信号量:用于控制多个线程对共享资源进行访问的工具。
链表和数组有什么区别
- 内存存储方式
数组:数组在内存中是连续存储的,即数组的所有元素都占用一段连续的内存空间。这种存储方式使得数组在访问元素时非常高效,因为可以通过索引直接计算出元素在内存中的地址。然而,这也意味着数组的大小在定义时就需要确定,并且在整个生命周期中通常保持不变(尽管有些语言支持动态数组,但底层实现可能涉及内存重新分配和元素复制)。
链表:链表在内存中的存储是非连续的,每个节点都包含数据部分和指向下一个节点的指针(或引用)。这种存储方式使得链表在插入和删除元素时非常灵活,因为只需要修改相关节点的指针即可,而不需要移动其他元素。但是,访问链表中的元素需要从头节点开始遍历,直到找到目标元素,因此访问效率相对较低。 - 操作特性
数组:
访问元素:由于内存连续,访问速度快,时间复杂度为O(1)。
插入和删除:在数组中间或前端插入/删除元素时,需要移动大量元素以保持连续性,效率较低,时间复杂度通常为O(n)。
链表:
访问元素:需要从头节点开始遍历,访问效率较低,时间复杂度为O(n)。
插入和删除:只需修改相关节点的指针,效率较高,时间复杂度为O(1)(不考虑查找目标元素的时间)。 - 使用场景
数组:适用于需要频繁访问元素,但插入和删除操作较少的场景。例如,用于存储固定大小的数据集,或者作为缓存、队列等数据结构的基础。
链表:适用于需要频繁进行插入和删除操作,但访问元素频率不高的场景。例如,实现动态数据结构(如动态数组、栈、队列等)、图论中的邻接表等。 - 拓展性
数组:大小在定义时确定,不易动态扩展。当需要存储更多元素时,可能需要重新分配更大的内存空间,并复制原数组的元素,这可能会导致性能下降。
链表:大小可以动态变化,只需在需要时创建新的节点并将其添加到链表中即可。这使得链表在存储大量数据或数据大小不确定时更加灵活。
map底层是什么
在C++中,map是一种关联容器,它存储元素并形成键值对。但需要注意的是,C++中的map并不直接基于哈希表实现,而是通常使用红黑树(一种自平衡二叉查找树)来实现。红黑树能够保证在最坏情况下,插入、删除和查找操作的时间复杂度都是O(log n)。这使得C++的map在保持有序性的同时,也具有较高的操作效率。
什么是死锁,如何避免死锁?遇到了怎么办?
死锁是系统中两个或多个进程在执行过程中,因争夺资源而造成的一种僵局。当每个进程都持有一定的资源并等待其他进程释放它们所需的资源时,如果这些资源都被其他进程占有且不释放,就导致了死锁。
死锁只有同时满足以下四个条件才会发生:
- 互斥条件:一个进程占用了某个资源时,其他进程无法同时占用该资源。
- 请求保持条件:一个线程因为请求资源而阻塞的时候,不会释放自己的资源。
- 不可剥夺条件:资源不能被强制性地从一个进程中剥夺,只能由持有者自愿释放。
- 循环等待条件:多个进程之间形成一个循环等待资源的链,每个进程都在等待下一个进程所占有的资源。
避免死锁:通过破坏死锁的四个必要条件之一来预防死锁。比如破坏循环等待条件,让所有进程按照相同的顺序请求资源。 检测死锁:通过检测系统中的资源分配情况来判断是否存在死锁。例如,可以使用资源分配图或银行家算法进行检测。 解除死锁:一旦检测到死锁存在,可以采取一些措施来解除死锁。例如,可以通过抢占资源、终止某些进程或进行资源回收等方式来解除死锁。
什么是野指针?如何避免?怎么排查?
什么是野指针
野指针是指"指向已经被释放的或无效的内存地址的指针"。在 C 和 C++ 这类允许直接操作内存地址的语言中,如果指针没有被正确初始化,或者指针所指向的内存已经被释放,那么这个指针就成为了野指针。使用野指针可能会导致程序崩溃、数据损坏或者其他一些不可预测的行为。
1.在什么情况下会产生野指针?
- 在释放后没有置空指针: 使用
delete 或 free 释放了内存后,没有将指针设置为 nullptr,指针仍然指向已释放的内存地址。 - 返回局部变量的指针 : 如果函数返回了指向其局部变量的指针,一旦函数返回,这些局部变量的生命周期结束,返回的指针成为野指针。
- 越界访问:指针访问的内存超出了其合法的内存块边界。
- 函数参数指针被释放。
2.如何避免野指针 - 在释放内存后将指针置为 nullptr 。
- 避免返回局部变量的指针。
- 使用智能指针(如 std::unique_ptr 和 std::shared_ptr )。
- 注意函数参数的生命周期,避免在函数内释放调用方传递的指针,或者通过引用传递指针。
如何排除野指针
使用工具:利用静态分析工具(如Clang Static Analyzer)和动态分析工具(如Valgrind)来检测野指针和内存泄漏。
代码审查:定期进行代码审查,检查是否有未初始化的指针、内存泄漏、重复释放等问题。
单元测试:编写单元测试来覆盖指针操作的代码路径,确保它们在不同情况下都能正常工作。
重构代码:如果发现频繁出现野指针或内存泄漏,考虑重构代码,使用更安全的编程模式(如使用智能指针)来减少这些错误。
调试:当程序出现崩溃或异常行为时,使用调试器(如GDB、LLDB)来检查指针的值和指向的内存区域,查找野指针的根源。
使用单例模式,有什么优点
单例模式确保全局只有一个这样的实例,避免了资源的重复创建和浪费。
方便管理使用:有时我们需要在多个类之间共享某个功能,而这些功能如果分散在多个实例中将会变得难以管理。通过单例模式,我们可以将这个功能封装在一个单独的类中,并确保整个应用只使用这个类的单一实例。
II2C通信:一个主机两个从机,出现问题怎么排查?
一、基本检查
连接检查:
确保所有设备的I2C接口(SCL和SDA线)都正确连接,没有松动或短路。
检查是否有其他信号线(如电源线、地线)干扰I2C总线。
电源检查:
确保所有从机设备都有稳定的电源供应,并且电源电压符合设备规格要求。
二、硬件检查
上拉电阻:
检查SDA和SCL线上是否连接了适当的上拉电阻。上拉电阻的阻值应根据总线的速率和负载电容来确定,以确保信号的稳定性和可靠性。
地址检查:
确认两个从机的I2C地址是否设置正确,并且没有冲突。在通信时,主机需要发送正确的从机地址来与从机进行通信。
设备状态:
确保从机设备处于可以接收通信的状态,例如没有处于复位状态或低功耗模式。
三、软件检查
初始化:
检查I2C总线的初始化代码是否正确,包括时钟速率、从机地址等设置。
通信协议:
确保通信协议符合I2C规范,包括起始条件、停止条件、应答信号(ACK)等。
数据传输:
检查数据传输过程中是否有错误发生,如校验和错误、数据冲突等。
四、使用工具进行诊断
I2C分析仪:
使用I2C分析仪连接到I2C总线,捕获通信过程中的数据包。通过分析数据包,可以检查起始条件、停止条件、地址、数据等是否正确。
分析仪还可以显示时钟信号和数据信号之间的时序关系,帮助检查是否存在时序问题。
示波器:
使用示波器观察SDA和SCL信号的波形,检查信号是否稳定、有无畸变。同时,可以测量信号的上升沿和下降沿时间,以及高电平和低电平的持续时间。
五、解决步骤
隔离问题:
尝试单独与一个从机进行通信,看是否能够成功。如果单独通信正常,则可能是另一个从机或总线连接有问题。
调整参数:
如果发现时序问题,可以尝试调整I2C总线的时钟速率,或者更换上拉电阻的阻值。
更换设备:
如果怀疑某个从机设备有问题,可以尝试更换设备进行测试。
软件调试:
如果问题仍然存在,可能需要检查并调试软件的I2C通信代码。