进程相关面试题 生产者消费者模型

 

 

 

进程相关的题目:

进程在内核中的数据结构是什么?

  • 多线程解决了什么问题
  • 同步非阻塞IO优点是什么?减少了阻塞时间有什么用?
  • 在cpu拉满的情况下,linux的timefd定时会出现延时吗?如何保证没有延时的呢?(软中断,进程优先级),这个软中断是由谁发起的呢(内核)?
  • 多线程和多进程有什么区别?多进程之间共享什么?多线程之间共享什么?进程切换和线程切换
  • 进程间通信,管道的底层原理是什么?信号量的底层原理是什么?
  • 生产者消费者模型
  • 进程调度

锁相关的题目

  • 接触过什么锁?底层的实现是什么?各自用在什么场景下?
  • 读写锁应用场景,实现一个读写锁。给定一个原子变量,如何实现自旋锁?
  • 条件变量的使用方法?
  • 线程安全与可重入的概念
  • 智能指针,shared_ptr线程安全吗
  • 多进程资源互斥
  • STL的容器是线程安全的吗?
  • Linux上的erron是线程安全的吗?

 

多线程和多进程有什么区别?多进程之间共享什么?多线程之间共享什么?进程切换和线程切换

进程占有的资源:地址空间,全局变量,打开的文件,子进程,信号量,账户信息

线程占有的资源:栈,寄存器,状态,程序计数器

最后关键:线程占有的资源不会共享,所以堆是共享的

https://www.cnblogs.com/wuchanming/p/4378189.html

这里注意下错误返回码,以及程的信号屏蔽码在不同线程间是不一样的。

 

 

 

生产者消费者模型  

生产者消费者模型最简单的模型为单个消费者,单个生产者,以及无限大的缓冲区。以下是生产者,消费者在linux C下面的实现

1.信号量的实现方式

先贴下信号量使用的函数

sem_t xxx;
sem_wait();
sem_post();
sem_init();

 

#include
#include
#include

#define Max 5
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

sem_t full ;
sem_t empty;

int end = 0 ;
int front = 0;

void* produce(void* arg){
    while (1)
    {
        /* code */
        sem_wait(&empty);
        pthread_mutex_lock(&mutex);

        end = (end + 1)%Max;
        printf("now end is %d\n",end);
        pthread_mutex_unlock(&mutex);
        sem_post(&full);
    }
}

void* consume(void* arg){
    while (1)
    {
        sem_wait(&full);
        pthread_mutex_lock(&mutex);

        front = (front + 1)%Max;
        printf("now front is %d\n",front);
        pthread_mutex_unlock(&mutex);
        sem_post(&empty);
    }

}

int main(int argc, char *argv[])
{
	pthread_t thid1;
	pthread_t thid2;

	sem_init(&full, 0, 0);
	sem_init(&empty, 0, 5);
 
	pthread_create(&thid1, NULL, produce, NULL);
	pthread_create(&thid2, NULL, consume, NULL);

	pthread_join(thid1, NULL);
	pthread_join(thid2, NULL);

	return 0;
}

 

gcc -g test.cpp -o main -lpthread

2.使用条件变量和互斥锁 (知乎也有https://www.zhihu.com/question/36902051)

条件变量的常见用法是在不满足某些条件时,阻塞自己,直到有线程通知自己醒来。

而互斥量在这里的作用依然还是防止多线程对共享资源同时操作,造成未知结果。

生产者消费者的行为与之前相同,只不过原来只调用sem_wait()可以完成两步,1是检查条件,2是阻塞,现在条件变量需要我们自己来设定条件(所以说条件变量配合互斥锁比信号量的功能更强大,因为它可以自定义休眠条件,但是这对使用者的要求也提高了,必须理清逻辑关系避免死锁)
 

这里需要关注下条件变量的底层原理

pthread_wait是先将互斥锁解开,并陷入阻塞,直到pthread_signal发出信号后pthread_cond_wait才再加上锁,然后退出。

所以条件变量是配合锁一起使用的,一般的格式是

pthread_mutex_lock(&lock);
while(xxx条件){
    pthread_cond_wait(¬empty,&mutex);
}

.....
pthread_mutex_unlock(&lock);
#include
#include
#include

#define Max 5
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t notfull = PTHREAD_COND_INITIALIZER;
pthread_cond_t notempty = PTHREAD_COND_INITIALIZER;



int end = 0 ;
int front = 0;

void* produce(void* arg){
    while (1)
    {
        /* code */
        pthread_mutex_lock(&mutex);
        while ((end+1)%Max == front)
        {
            /* code */
            printf("full !!!!  Productor is waiting\n");
            pthread_cond_wait(¬full,&mutex);
        } 
        end = (end + 1)%Max;
        printf("now end is %d\n",end);


        pthread_cond_signal(¬empty);
        pthread_mutex_unlock(&mutex);
 
    }
}

void* consume(void* arg){
    while (1)
    {

        pthread_mutex_lock(&mutex);
        while (end%Max == front)
        {
            printf("Empty!!! Comsumer is waiting\n");
            pthread_cond_wait(¬empty,&mutex);
        }
        
        front = (front + 1)%Max;
        printf("now front is %d\n",front);

        pthread_cond_signal(¬full);
        pthread_mutex_unlock(&mutex);

    }

}

int main(int argc, char *argv[])
{
	pthread_t thid1;
	pthread_t thid2;

	pthread_create(&thid1, NULL, produce, NULL);
	pthread_create(&thid2, NULL, consume, NULL);

	pthread_join(thid1, NULL);
	pthread_join(thid2, NULL);

	return 0;
}

 

锁相关的题目(这部分内容可以联合数据库中锁来讲)

  • 接触过什么锁?底层的实现是什么?各自用在什么场景下?
  • 读写锁应用场景,实现一个读写锁。给定一个原子变量,如何实现自旋锁?
  • 条件变量的使用方法?
  • 智能指针,shared_ptr线程安全吗
  • 多进程资源互斥

 

进程调度

进程调度

这里面需要注意的知识点是:进程调度会把“缺乏资源”的进程置于休眠队列、又要把休眠队列中资源要求可以满足的进程置于等待队列——然后时间片一到就会调度运行。

(那么,我们可以用一些分析工具看出,多少进程阻塞在磁盘访问上了,这个是从何而来的呢?答案是,操作系统会维护一个“锁”的列表;找到这个锁的对应项,读它的相关信息,再找到申请它的进程队列,自然就知道哪些进程试图访问磁盘、但暂时得不到满足了——注意这类锁并不需要进程显式申请,相关逻辑已经包含在对应的系统调用里了)

 

 

接触过什么锁?底层的实现是什么?各自用在什么场景下?

使用方法:https://www.zhihu.com/question/66733477 用户 果冻虾仁

知乎invalid s的回答 https://www.zhihu.com/question/66733477

自旋锁:如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直申请CPU时间片轮询自旋锁,直到获取为止,一般应用于加锁时间很短(1ms左右或更低)的场景。(主要是能够减少CPU上下文的切换)

​这可以避免进程因被识别为“资源不足”而被操作系统置入休眠队列,从而避免不必要的上下文切换开销;但缺点是,它会导致“申请不到锁时执行死循环”,使得CPU核心占用100%——如果是单核单线程CPU,它就白白发一个时间片的热然后失去执行权(因为它占用了时间片,导致能释放资源给它的进/线程压根得不到执行机会);只有在多CPU和/或多核和/或多线程硬件平台上、且这个锁一定会在远短于一个时间片的时间内被请求到,它才可能真正提高效率(否则又是白白浪费时间、电力让CPU发热了)。

 

互斥锁:无法获取琐时,进线程立刻放弃剩余的时间片并进入阻塞(或者说挂起)状态,同时保存寄存器和程序计数器的内容(保存现场,上下文切换的前半部分),当可以获取锁时,进线程激活,等待被调度进CPU并恢复现场(上下文切换下半部分)
上下文切换会带来数十微秒的开销,不要在性能敏感的地方用互斥锁


读写锁:写锁被占用时,所有申请读锁和写锁的进线程都会被阻塞,读锁被占用时,申请写锁的进线程被阻塞,其他申请读锁的进线程不会。

条件变量:....

怎么进行选择呢?

进行压测吧....

 

读写锁应用场景,实现一个读写锁。给定一个原子变量,如何实现自旋锁?

https://blog.csdn.net/ojshilu/article/details/25244389

读写锁:有读者和写者两组进程,共享一个文件。1.允许多个读者对文件进行操作 2.同一时刻只能有一个写者

分析:读者与写者之间是互斥的。写者之间也是互斥的。这里实现的关键是有一个互斥访问的计数器count。

1.使用信号量实现(可以参考王道考研上面的实现)

 

使用条件变量的方式

https://blog.csdn.net/shichao1470/article/details/89856443

调用pthread_cond_wait前需要先对互斥量mutex上锁,才能把&mutex传入pthread_cond_wait函数
在pthread_cond_wait函数内部,会首先对传入的mutex解锁
当等待的条件到来后,pthread_cond_wait函数内部在返回前会去锁住传入的mutex
 

线程安全与可重入的概念

线程安全,多个线程同时操作一个对象,在各种不同情况下,都不会造成不同的后果。

https://blog.csdn.net/xiarendeniao/article/details/7277709

https://blog.csdn.net/tennysonsky/article/details/45127125?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param

之前还看过一个更加通俗易懂的解释,不过暂时找不到了

 

智能指针,shared_ptr线程安全吗

参考陈硕的https://blog.csdn.net/Solstice/article/details/8547547

shared_ptr并不是100%的线程安全,因为他内部有两个对象,一个是引用计数,另外一个是引用的对象,所以没法进行原子操作。

结论是:shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。分析见陈硕。   虽然通过原子操作解决了引用计数的计数的线程安全问题, 但是智能指针指向的对象的线程安全问题,智能指针没有做任何的保证。 

引用

 

 

STL的容器是线程安全的吗?

所以,结论是并不是线程安全的。

https://www.cnblogs.com/ztteng/p/3411738.html

对于多线程的支持,仅限于一下情况:

1.多个线程同时读同一个容器

2.多个线程操作不同的容器

若对同一个容器,有读也有写就需要自己保证了

需要程序员自己来控制,比如:线程A读容器某一项时,线程B正在移除该项。这会导致一下无法预知的错误。
通常的解决方式是用开销较小的临界区(CRITICAL_SECTION)来做同步。以下列方式同步基本上可以做到线程安全的容器(就是在有写操作的情况下仍能保证安全)。

1.每次调用容器的成员函数的期间需要锁定。
2.每个容器容器返回迭代器的生存期需要锁定。
3.每个容器在调用算法的执行期需要锁定。

 

C++STL容器部分操作时多线程不安全的。在多线程情景下要使用STL容器必须考虑到这种情景

 

根据这个模式 自己封装一个 thread safe 的vector。每次操作函数的时候  std::lock_guard lock(other.m_);

参考github上的

https://github.com/LWeiFreshaman/STL_thread/blob/master/vector_thread.h

https://github.com/Abbeychenxi/threadsafe-stl/blob/master/stack-pthread.cpp

 

 

Linux上的erron是线程安全的吗?

https://blog.csdn.net/weixin_35695879/article/details/89530410

1)errno是一个整型变量,当系统调用和一些库函数发生错误时会通过设置errno的值来告诉调用者出了什么问题。
2)errno的有效值都是非零的。(这个manpage有个悖论,第二段中说,errno从来不能被设为0,而在第三段又说有些接口会将其设置为0)
3)errno在ISO C标准中定义的,它可能是一个宏并且不能被显示声明(也就是重新定义)。
4)errno是线程安全的,在一个线程中设置它,不会影响别的线程对它的使用。这一点很重要,不知道有没有像我之前有过这样的问题:看起来errno像是一个全局的变量,应该不是线程安全的吧?看了这个之后,就有答案了,errno是thread-local的,也就是每个线程都有一个。具体见https://blog.csdn.net/xiaofei0859/article/details/4939733,有详细说明errono为啥是线程安全的

 

4. errno使用的注意事项:
1)当使用系统调用和库函数时,除了函数的返回值,记得还要考虑errno的值啊。
2)并不是所有的系统调用活着库函数都会设置errno的值,如果你的程序对它有依赖,需要开发人员在接口错误处理中,手工设置。

Thread_Local是什么东西

你可能感兴趣的:(秋招复习)