判断题
-
不论是系统支持线程还是用户级线程,其切换都需要内核的支持(F)
用户态线程的切换在用户态实现,不需要内核支持。 -
线程包含CPU现场,可以独立执行程序(F)
线程包含cpu现场,但是线程只是进程中的一个执行流,执行的是程序中的一个片段的代码,多个线程共同完成整体程序的运行。 -
线程和进程都可并发执行,线程的粒度小于进程,占用资源更少,因此通常多线程比多进程并发性更高(T)
-
ps命令用于查看进程信息,其中-L选项用于查看轻量级进程信息(T)
-
pthread_self() 用于获取轻量级进程ID(F)
pthread_self() 用于获取用户态线程的tid。 -
在有多个线程的情况下,主线程从main函数的return返回或者调用pthread_exit函数,则整个进程退出(F)
主线程调用pthread_exit只是退出主线程,并不会导致进程的退出。 -
在linux 中,进程比线程安全的原因是进程之间不会共享数据(F)
进程比线程安全的原因是每个进程有独立的虚拟地址空间,有自己独有的数据,具有独立性。不会数据共享这个太过宽泛与片面。 -
对于大量的计算优先使用多进程(F)
大量的计算使用多进程和多线程都可以实现并行/并发处理,多线程的资源消耗小于多进程,但稳定性较多进程有所不如,因此还要看具体更加细致的需求场景。 -
一个程序至少有一个进程,一个进程至少有一个线程(F)
程序是静态的,不涉及进程,进程是程序运行时的实体,是一次程序的运行。 -
线程自己不拥有系统资源(T)
进程是资源的分配单位,所以线程并不拥有系统资源,而是共享使用进程的资源,进程的资源由系统进行分配。 -
条件变量和信号量可用来实现线程间通知和唤醒(T)
线程间的通知和唤醒以及线程的等待,这些是线程间实现同步的基础,而信号量和条件变量通过提供使线程等待和唤醒的功能,而被用于实现线程间的同步。 -
在生产者与消费者模型中只需要一个条件变量就可以(F)
条件变量使用时,不同的角色需要等待在不同的条件变量等待队列中,防止角色被误唤醒。 -
银行家算法可以预防死锁(F)
银行家算法的思想在于将系统运行分为两种状态:安全/非安全(有可能出现风险的都属于非安全)。
银行家算法,在分配资源前进行风险判断,避免风险的发生。是避免出现死锁的一种算法(并非预防的方法)。主要是避免了出现“环路等待”的条件。 -
信号量允许多个线程同时使用共享资源(T)
信号量主要用于实现同步操作,只要信号量资源数大于0就表示可获取,可访问。
若要使用信号量模拟实现互斥,则需要初始化资源计数为1,表示资源只有一个,则只有一个执行流能访问。 -
如果只是在进程内部使用的话,使用临界区会带来速度上的优势并能够减少资源占用量(T)
临界区技术:实现串行化来访问共享资源的代码片段。速度比较快但是只能用于同一进程的线程间。
问答题
-
请简述什么是LWP?
LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化。 -
请简述LWP与pthread_create创建的线程之间的关系?
pthread_create是一个库函数,功能是在用户态创建一个用户线程,而这个线程的运行调度是基于一个轻量级进程实现的。 -
简述轻量级进程ID与进程ID之间的区别?
因为Linux下的轻量级进程是一个pcb,每个轻量级进程都有一个自己的轻量级进程ID(pcb中的pid),而同一个程序中的轻量级进程组成线程组,拥有一个共同的线程组ID。 -
请简述什么是线程互斥,为什么需要互斥?
线程互斥指的是,在多个线程对临界资源进行争抢访问时有可能会造成数据二义,因此需要通过保证同一时间只有一个线程能够访问临界资源的方式,来实现线程对临界资源的访问安全性。 -
请简述什么是线程同步,为什么需要同步?
线程同步指的是线程间对数据资源进行获取,有可能在不满足访问资源条件的情况下就访问资源,而造成程序逻辑混乱。因此通过进行条件判断来决定线程在不满足条件时休眠等待或满足条件后唤醒的方式实现对资源访问的合理性。 -
请简述线程安全概念与实现?
线程安全指的是在多线程编程中,多个线程对临界资源进行争抢访问而不会造成数据二义或程序逻辑混乱的情况。
线程安全的实现,通过同步与互斥实现。 -
信号量与条件变量有什么区别?
条件变量:提供了一个pcb阻塞队列以及阻塞和唤醒线程的接口用于实现同步,但是什么时候该唤醒以及什么时候该阻塞线程由程序员进行控制,而这个控制通常需要一个共享资源的条件判断完成,因此条件变量还需要搭配互斥锁使用,来保护这个共享资源的条件判断及操作。
信号量:提供一个pcb等待队列,以及一个实现了原子操作的对资源进行计数的计数器,通过自身计数器实现同步的条件判断,因此不需要搭配互斥锁使用,而且信号量在初始化计数为1的情况下也可以模拟实现互斥操作。 -
请简述线程池的作用与实现原理?
线程池通过一个线程安全的阻塞任务队列加上一个或一个以上的线程实现,线程池中的线程可以从阻塞队列中获取任务进行任务处理,当线程都处于繁忙状态时可以将任务加入阻塞队列中,等到其中的线程空闲后进行处理。
线程池可以避免大量线程频繁创建或销毁所带来的时间成本,也可以避免在峰值压力下,系统资源耗尽的风险;并且可以统一对线程池中的线程进行管理,调度,监控。 -
如何理解原语的原子性,在单机环境下如何实现原语的原子性,实现时应注意哪些问题?
所谓原语的原子性操作是指一个操作中的所有动作,要么成功完成,要么全不做。也就是说,原语操作是一个不可分割的整体。为了保证原语操作的正确性,必须保证原语具有原子性。在单机环境下,操作的原子性一般是通过关闭中断来实现的。由于中断是计算机与外设通信的重要手段,关闭中断会对系统产生很大的影响,所以在实现时一定要避免原语操作花费时间过长,绝对不允许原语中出现死循环。
程序题
完成两个线程通过条件变量实现交替打印的控制
要求:线程A打印-我是线程A;线程B打印-我是线程B; 最终实现交替打印,不能出现连续的相同打印。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 互斥锁
pthread_mutex_t g_lock;
// 同步
pthread_cond_t thread_a_cond;
pthread_cond_t thread_b_cond;
int g_is_my_turn = 0;
void *thread_a_start(void *arg)
{
(void)arg;
while (1)
{
pthread_mutex_lock(&g_lock);
while (g_is_my_turn == 1)
{
pthread_cond_wait(&thread_a_cond, &g_lock);
}
printf("线程A打印-我是线程A\n");
g_is_my_turn++;
pthread_mutex_unlock(&g_lock);
pthread_cond_signal(&thread_b_cond);
}
}
void *thread_b_start(void *arg)
{
(void)arg;
while (1)
{
sleep(1);
pthread_mutex_lock(&g_lock);
while (g_is_my_turn == 0)
{
pthread_cond_wait(&thread_b_cond, &g_lock);
}
printf("线程B打印-我是线程B\n");
g_is_my_turn--;
pthread_mutex_unlock(&g_lock);
pthread_cond_signal(&thread_a_cond);
}
}
int main()
{
pthread_mutex_init(&g_lock, nullptr);
pthread_cond_init(&thread_a_cond, nullptr);
pthread_cond_init(&thread_b_cond, nullptr);
pthread_t thread_a, thread_b;
int ret = pthread_create(&thread_a, nullptr, thread_a_start, nullptr);
if (ret < 0)
{
perror("pthread_create");
return 0;
}
ret = pthread_create(&thread_b, nullptr, thread_b_start, nullptr);
if (ret < 0)
{
perror("pthread_create");
return 0;
}
pthread_join(thread_a, nullptr);
pthread_join(thread_b, nullptr);
pthread_mutex_destroy(&g_lock);
pthread_cond_destroy(&thread_a_cond);
pthread_cond_destroy(&thread_b_cond);
return 0;
}