C/C++基础知识点——linux多线程编程
线程、进程的区别
线程:CPU调度的最小单元
进程:进行中的程序,资源分配的最小单元
线程和进程的区别及联系:
- 一个进程可以有多个线程,但至少要有一个进程
- 多个进程共享一个进程中的所有资源,而多个进程之间是没有联系的
- 一个进程中的某一线程中断,会造成整个进程的退出或者崩溃,而对于多个进程某一进程的中断不会影响其它进程的执行
- 多进程的开销要比多线程的开销大一倍
并行和并发的区别
- 并行是同时处理多个任务,而并发是可处理多个任务,但不一定同时
- 并行多用于多核处理,而并发多用于单核处理
多线程和多进程的区别以及应用场景
多进程:ngnix(master主进程管理多个work子进程)
多线程:redis 6.0(多个ID线程处理数据结构)
实现多线程有那些方式
在C++11未对多线程进行合并之前,Linux采用pthread_create(threadID, threadType, 指针函数,函数参数)
在C++11之后统一采用thread,调用join和detach两个接口
QT中多线程的实现方式,有以下两种方式:
- 继承Qthread类,重写run方法
- 直接使用moveToThread()接口将QObject子类移至线程中,内部的所有信号的槽函数均在线程中执行
- 使用QTConcurrent::run()类
sleep方法有什么作用,一般用来做什么
sleep是线程类Thread的方法,作用是使当前线程按时睡眠,可以放到任何位置
线程有哪几种状态,是如何转换的
运行期、挂起、死亡、正常退出及线程阻塞
线程同步,线程安全
线程安全:指当多个线程同时访问某一共享数据时,加一把锁,防止多个线程同时访问,当前时间只有一个线程访问,其他线程必须等待。
线程同步方式:事件、信号量、互斥量及临界区
死锁及死锁发生的条件
死锁:指两个或两个以上的线程在执行过程中,因抢占资源而造成一种相互等待的现象
死锁产生的原因:系统资源不足、线程推进顺序不当、资源分配不当
死锁的形成场景:忘记释放锁、单线程重复释放锁、多线程多锁申请、环形锁的申请
死锁的条件:互斥条件、请求和保持条件、不可抢占、循环等待
死锁的防止:
- 尽量避免同时只对一个互斥锁进行上锁;
- 互斥锁保护区域不要使用操作其它互斥锁的代码;
- 给锁定义顺序
死锁问题排查
- pstrack + 进程号
- 利用 GDB 工具进行排查
读写锁、自旋锁及互斥锁的区别
自旋锁与互斥锁的区别:
-
互斥锁是一种独占锁,互斥锁加锁失败后,线程会释放 CPU,给其他线程,而自旋锁加锁失败后,线程会忙等待,直到它拿到锁;
-
对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为「睡眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行;
-
互斥锁加锁失败时,会从用户态陷入到内核态,存在一定的性能开销成本,而自旋锁是通过 CPU 提供的 CAS 函数在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。
读写锁:
- 它由「读锁」和「写锁」两部分构成,如果只读取共享资源用「读锁」加锁,如果要修改共享资源则用「写锁」加锁。
- 写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有;
读写锁可以分为「读优先锁」和「写优先锁」:
读优先锁:读优先锁期望的是,读锁能被更多的线程持有,以便提高读线程的并发性,它的工作方式是:当读线程 A 先持有了读锁,写线程 B 在获取写锁的时候,会被阻塞,并且在阻塞过程中,后续来的读线程 C 仍然可以成功获取读锁,最后直到读线程 A 和 C 释放读锁后,写线程 B 才可以成功获取写锁。
写优先锁:
写优先锁是优先服务写线程,其工作方式是:当读线程 A 先持有了读锁,写线程 B 在获取写锁的时候,会被阻塞,并且在阻塞过程中,后续来的读线程 C 获取读锁时会失败,于是读线程 C 将被阻塞在获取读锁的操作,这样只要读线程 A 释放读锁后,写线程 B 就可以成功获取读锁。
上面两种情况,无论哪种方式都会造成【饥饿】现象,我了避免这种现象出现,提出公平读写锁(将读写锁加入队列中
进行排队)
线程间通信方式
事件、信号量、互斥量及临界区
事件
是win32提供的一种运行线程间通信的方式。事件可以是激活状态也可以是未激活状态
事件根据其状态的变迁可以分为:手动重置事件和自动重置事件
手动重置事件
手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而一直保持为激发状态,直到程序重新把它设置为未激发状态
自动重置事件
自动重置事件被设置为激发状态后,会唤醒一个等待的线程,然后自动恢复为未激发状态
注意:自动重置事件调用 setEvent 和 pulseEvent 有可能引起死锁,必须小心
信号量
信号量是解决生产者和消费者问题的关键因素,是维护0到指定最大值之间的同步对象。信号量计数大于0表示有信号量、等于0表示无信号量;用于有限量共享资源的访问。
信号量用于解决生产者-消费者问题(通过互斥锁和条件变量来实现)。
互斥量
一个线程占用某一共享资源,其他线程必须等待,直到该资源得到释放,其他资源才能访问
条件变量
与互斥锁不同的是,条件变量是用来等待而不是上锁的,条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用(为了防止发生竞争条件),避免大量加锁造成的资源浪费。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
- 一个线程等待“条件变量的条件成立”而挂起
- 另一个线程使“条件成立”(给出条件成立信号)
如何正确的使用条件变量
-
创建和销毁条件变量
创建条件变量:pthread_cond_init
销毁条件变量:pthread_cond_destroy -
等待条件的发生
条件不满足时,使用 pthread_cond_wait() / pthread_cond_timewait() // 可以指定超时时间 函数,来让线程进入休眠。当函数正常返回时,返回值为0。 -
条件触发(唤醒其他线程)
pthread_cond_signal()
pthread_cond_broadcast()
使用条件变量需要注意的地方
-
要考虑解锁和唤醒的顺序
由于 pthread_cond_signal() 和 pthread_cond_broadcast() 函数的调用都不需要加锁,所以它们放到 pthread_mutex_unlock() 之前或者之后执行都是可以的。但在实际使用中,需要根据具体情况考虑它们的顺序,来使得程序高效运行。 -
要使用 while 而不是 if,避免虚假唤醒
这是由于 wait 函数被唤醒时,存在虚假唤醒等情况,导致唤醒后发现,条件依旧不成立。因此需要使用 while 语句来循环地进行等待,直到条件成立为止。 -
timewait 是 absolute time
pthread_cond_timedwait() 函数的 abstime 指的是超时的绝对时间,而不是相对现在的时间间隔。这点经常会有人误会。 -
pthread_cond_timedwait() 不一定会准时返回
如果 pthread_cond_timedwait() 超时到了,但是这个时候 mutex 锁被其他线程持有,导致本线程不能锁定 mutex,无法进入临界区,那么 pthread_cond_timedwait() 就无法立即返回
互斥量和信号量的区别
- 互斥量用于线程的互斥,信号量用于线程的同步
- 互斥量的值只能为 0 或者 1 ,信号量的值为非负整数
- 互斥量的加锁和解锁必须由同一个线程执行,信号量可以由一个线程释放,另一个线程得到
sleep和wait区别
- sleep睡眠时,保持对象锁,仍然占有该锁,其他线程无法访问,休眠后自行恢复运行,无需其他线程唤醒
- wait睡眠时,释放对象锁,其他线程可以访问,休眠后需要其他线程进行唤醒
进程间通信方式
共享内存、管道、信号量、消息队列、socket套接字
一个进程可以创建多个线程,和什么有关?
由可用可用虚拟空间和线程的栈的大小共同决定。
本文来自博客园,作者:suntl,转载请注明原文链接:https://www.cnblogs.com/stlong/p/17628073.html