0
点赞
收藏
分享

微信扫一扫

操作系统之线程(面经总结)

架构大数据双料架构师 2022-03-16 阅读 74

3、线程

3.1、线程的状态

1、五种状态
  • 新建:新创建了一个线程对象。
  • 就绪:该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
  • 运行:就绪状态的线程获取了CPU,执行程序代码。
  • 阻塞(等待):线程因为某种原因放弃CPU使用权,暂时停止运行
  • 销毁
2、如何创建线程

​ pthread_create C++新增线程库。线程的创建是由AfxBeginThread()函数完成,函数中传参分别是线程回调函数、传递给线程回调函数的参数、优先级别、线程的堆栈值、创建线程时的初始状态和线程的安全属性。线程回调函数的创建格式为UINT MyCreateThread(LPVOID pParam),LPVOID表示任意类型的指针,一般传递创建线程的类的this指针。

#include <thread> 
void Fun_1();
void Fun_2();
unsigned int counter = 0;
std::mutex mtx;   //互斥锁
int main()
{
    std::thread thrd_1(Fun_1);
    std::thread thrd_2(Fun_2);
    thrd_1.join();
    thrd_2.join();
    system("pause");
    return 0;
}

3.2、线程共享的资源和独立的资源

1、共享的资源
  • 进程代码段
  • 进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)
  • 进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
2、独立的资源
  • 线程ID,每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。
  • 寄存器组的值,由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
  • 线程的堆栈,堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈, 使得函数调用可以正常执行,不受其他线程的影响。
  • 错误返回码,由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。所以,不同的线程应该拥有自己的错误返回码变量。
  • 线程的信号屏蔽码,由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。
  • 线程的优先级,由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。

3.3、线程间通信

1、条件变量

​ 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用,以免出现竞态条件。当条件不满足时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。总的来说互斥锁是线程间互斥的机制,条件变量则是同步机制。

2、自旋锁

​ 如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁那么自旋就是在浪费CPU做无用功,但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。

3、互斥锁

​ 一次只能一个线程拥有互斥锁,其他线程只有等待

​ 互斥锁是在抢锁失败的情况下主动放弃CPU进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下文的切换。互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)的效果可能不亚于使用自旋锁。

4、读写锁
  • 多个读者可以同时进行读
  • 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
  • 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

3.4、线程中的sleep和wait

  • wait:释放执行权,释放锁(指定时间内,当然也可以不指定时间,在这个时间内notify唤不醒)
  • sleep:释放执行权,不释放锁(相当于等待一定时间然后继续向下执行)

3.5、怎么回收线程

1、等待线程结束
int pthread_join(pthread_t tid, void** retval);

​ 主线程调用,等待子线程退出并回收其资源,类似于进程中wait/waitpid回收僵尸进程,调用pthread_join的线程会被阻塞。

  • tid:创建线程时通过指针得到tid值。
  • retval:指向返回值的指针。
2、结束线程
pthread_exit(void* retval);

​ 子线程执行,用来结束当前线程并通过retval传递返回值,该返回值可通过pthread_join获得。

  • retval:同上。
3、分离线程
int pthread_detach(pthread_t tid);

​ 主线程、子线程均可调用。主线程中pthread_detach(tid),子线程中pthread_detach(pthread_self()),调用后和主线程分离,子线程结束时自己立即回收资源。

  • tid:同上。
举报

相关推荐

0 条评论