2020 Meituan Autumn Recruitment C ++ Preguntas y respuestas seleccionadas de la entrevista (Parte 1)

1. Características del árbol B +

(1) La cantidad de palabras clave de cada nodo es igual a la cantidad de hijos. Las palabras clave de todos los nodos internos que no sean los más bajos son las palabras clave más grandes en el subárbol correspondiente, y los nodos internos más bajos contienen todas las palabras clave. .
(2) Excepto por el nodo raíz, cada nodo interno tiene hasta m hijos. [3]
(3) Todos los nodos hoja están en el mismo nivel de la estructura del árbol y no contienen ninguna información (pueden considerarse nodos externos o nodos que fallan en la búsqueda) Por lo tanto, la estructura del árbol siempre está equilibrada en la altura del árbol.

2. Las desventajas de CAS y sus soluciones

Las deficiencias de CAS son como problema ABA, problema de consumo de bloqueo de giro, problema de coherencia de compartición multivariable.1ABA
:
Descripción del problema: el hilo t1 cambia su valor de A a B, y luego de B a A. Al mismo tiempo, un hilo t2 quiere cambiar el valor de A a C. Pero cuando CAS verifica, encontrará que no hay ningún cambio, pero de hecho ha cambiado. Puede causar que falten datos.
Solución: CAS sigue siendo similar al bloqueo optimista, agregando un número de versión o una marca de tiempo de la misma manera que el bloqueo optimista de datos, como AtomicStampedReference
2. Spin consume recursos:
Descripción del problema: cuando varios subprocesos compiten por el mismo recurso, si gira Si no ha tenido éxito, la CPU siempre estará ocupada.
Solución: Destruye el bucle infinito for. Cuando se exceda un cierto tiempo o número de veces, return saldrá. El LongAddr agregado en JDK8 es similar a ConcurrentHashMap. Cuando compiten varios subprocesos, la granularidad se reduce y una variable se divide en múltiples variables para lograr el efecto de varios subprocesos que acceden a múltiples recursos.Finalmente, se llama a sum para combinarlos.
Aunque tanto la base como las celdas son modificadas de forma volátil, se siente que la operación de suma no está bloqueada y el resultado de la suma puede no ser tan preciso.
2. Problema de coherencia de uso compartido de múltiples variables:
Solución: la operación CAS es para una variable, si opera en múltiples variables,

  1. Se puede solucionar con un candado.
  2. Empaquetado en clases de objetos para resolver.

Más preguntas de entrevista de empresas de Internet de primera línea vx ¡preste atención a Zero Sound Academy para recibir!
Inserte la descripción de la imagen aquí

3. Sabiendo que una función rand7 () puede generar números aleatorios del 1 al 7, proporcione una función que pueda generar números aleatorios del 1 al 10

La solución se basa en un método llamado muestreo por rechazo. La idea principal es que, siempre que se genere un número aleatorio dentro del rango objetivo, se devuelve directamente. Si el número aleatorio generado no está dentro del rango objetivo, descarte el valor y vuelva a muestrear. Dado que los números en el rango objetivo se seleccionan con la misma probabilidad, se genera tal distribución uniforme.

Obviamente, rand7 debe ejecutarse al menos dos veces, de lo contrario, no se generará el número 1-10. Al ejecutar rand7 dos veces, se pueden generar enteros del 1 al 49,

Inserte la descripción de la imagen aquí

Dado que 49 no es un múltiplo de 10, debemos descartar algunos valores. El rango de números que queremos es del 1 al 40. Si no está en este rango, descartar y volver a muestrear.

Código:

int rand10() {
    
    
 int row, col, idx;
 do {
    
    
 row = rand7();
 col = rand7();
 idx = col + (row-1)*7;
 } while (idx > 40);
 return 1 + (idx-1)%10;
}
 

Dado que el rango de filas es 1-7 y el rango de columnas es 1-7, el rango de valores idx es 1-49. Los valores superiores a 40 se descartan, de modo que los números restantes en el rango de 1 a 40 se devuelven por módulo. Calculemos el valor esperado del número de tiempos de muestreo que cumple con el rango de 1 a 40:Inserte la descripción de la imagen aquí

4. C ++ 11 tres formas de crear subprocesos

  1. A través del
    hilo de función : clase de biblioteca estándar
    unirse: bloquear el hilo principal y esperar
// MultiThread.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<map>
#include<string> 
#include<thread>
 
using namespace std;
void myPrint()
{
    
    
 cout << "线程开始运行" << endl;
 cout << "线程运行结束了" << endl;
}
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 可调用对象
 my2Obj.join();// 主线程阻塞在这,并等待myPrint()执行完
 cout << "wangtao" << endl;
 return 0;
}
 
detach(): 将主线程和子线程完全分离,子线程会驻留在后台运行,被C++运行时库接管,失去控制
 
void myPrint()
{
    
    
 cout << "线程开始运行1" << endl;
 cout << "线程开始运行2" << endl;
 cout << "线程开始运行3" << endl;
 cout << "线程开始运行4" << endl;
 cout << "线程开始运行5" << endl;
 cout << "线程开始运行6" << endl;
 cout << "线程开始运行7" << endl;
 cout << "线程开始运行8" << endl;
 cout << "线程开始运行9" << endl;
 
}
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 主线程阻塞在这,并等待myPrint()执行完
 my2Obj.detach();
 cout << "wangtao1" << endl;
 cout << "wangtao2" << endl;
 cout << "wangtao3" << endl;
 cout << "wangtao4" << endl;
 cout << "wangtao5" << endl;
 cout << "wangtao6" << endl;
 cout << "wangtao7" << endl;
 cout << "wangtao8" << endl;
 return 0;
}
 
joinable():判断是否可以成功使用join()或者detach()
 
程序说明:detach后不能在实施join
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 cout << "1:joinable() == true" << endl;
 }
 else {
    
    
 cout << "1:joinable() == false" << endl;
 }
 my2Obj.detach();
 
 if (my2Obj.joinable()) {
    
    
 cout << "2:joinable() == true" << endl;
 }
 else {
    
    
 cout << "2:joinable() == false" << endl;
 }
 cout << "wangtao1" << endl;
 cout << "wangtao2" << endl;
 cout << "wangtao3" << endl;
 cout << "wangtao4" << endl;
 cout << "wangtao5" << endl;
 cout << "wangtao6" << endl;
 cout << "wangtao7" << endl;
 cout << "wangtao8" << endl;
 return 0;
}
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.join();
 }
 cout << "wangtao1" << endl;
 cout << "wangtao2" << endl;
 cout << "wangtao3" << endl;
 cout << "wangtao4" << endl;
 cout << "wangtao5" << endl;
 cout << "wangtao6" << endl;
 cout << "wangtao7" << endl;
 cout << "wangtao8" << endl;
 return 0;
}
 
 
2.通过类对象创建线程
class CObject
{
    
    
public:
 void operator ()() {
    
    
 cout << "线程开始运行" << endl;
 cout << "线程结束运行" << endl;
 }
};
 
 
int main()
{
    
    CObject obj;
 std::thread my2Obj(obj); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.join();
 }
 cout << "see you " << endl;
 
 return 0;
}
 
 
class CObject
{
    
    
 int& m_obj;
public:
 CObject(int& i) :m_obj(i) {
    
    }
 void operator ()() {
    
     // 不带参数
 cout << "线程开始运行1" << endl;
 cout << "线程开始运行2" << endl;
 cout << "线程开始运行3" << endl;
 cout << "线程开始运行4" << endl;
 cout << "线程开始运行5" << endl;
 }
};
int main()
{
    
    
 int i = 6;
 CObject obj(i);
 std::thread my2Obj(obj); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.detach();
 }
 cout << "see you " << endl;
 
 return 0;
}detach() 主线程结束对象即被销毁,那么子线程的成员函数还能调用吗?
这里的的对象会被复制到子线程中,当主线程结束,复制的子线程对象并不会被销毁
只要是没有引用、指针就不会出现问题 
通过复制构造函数和析构函数来验证对象是否复制到了子线程中 
// MultiThread.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<map>
#include<string> 
#include<thread>
using namespace std;
class CObject
{
    
    
 int& m_obj;
public:
 CObject(int& i) :m_obj(i) {
    
    
 cout << "ctor" << endl;
 }
 CObject(const CObject& m) :m_obj(m.m_obj) {
    
    
 cout << "copy ctor" << endl;
 }
 ~CObject(){
    
    
 cout << "dtor" << endl;
 }
 void operator ()() {
    
     // 不带参数
 cout << "线程开始运行1" << endl;
 cout << "线程开始运行2" << endl;
 cout << "线程开始运行3" << endl;
 cout << "线程开始运行4" << endl;
 cout << "线程开始运行5" << endl;
 }
};
int main()
{
    
    
 int i = 6;
 CObject obj(i);
 std::thread my2Obj(obj); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.detach();
 }
 cout << "see you " << endl;
 
 return 0;
}

El destructor del hilo secundario se ejecuta en segundo plano, por lo que el dtor de salida es el hilo principal. El resultado de join () es:
3. Crear hilo a través de expresión lambda

int main()
{
    
    
 auto myLamThread = [] {
    
    
 cout << "线程开始运行" << endl;
 cout << "线程结束运行" << endl;
 };
 thread cthread(myLamThread);
 cthread.join();
 std::cout << "see you " << endl;
 
 return 0;
}

6. Habla sobre las funciones en línea de C ++

Función en línea en línea: El propósito de introducir la función en línea es resolver el problema de eficiencia de la llamada de función en el programa. Digámoslo de esta manera, cuando el compilador compila el programa, el compilador usará la expresión de llamada de función en línea en el programa. El cuerpo de la función de la función asociativa se reemplaza y, para otras funciones, se reemplaza en tiempo de ejecución. En realidad, se trata de un ahorro de espacio y tiempo. Entonces, las funciones en línea son generalmente funciones pequeñas con líneas 1-5. Tenga cuidado al usar funciones en línea:
1. Las declaraciones de bucle y las declaraciones de cambio no están permitidas
en la función en línea ; 2. La definición de la función en línea debe aparecer antes de la primera llamada de la función en línea;
3. La ubicación en la estructura de clases La clase indica que la función definida internamente es una función en línea.

7. Ventajas y desventajas de las macrodefiniciones

ventaja:

  1. Mejorar la legibilidad del programa, pero también facilitar la modificación;
  2. Mejorar la eficiencia operativa del programa: el uso de definiciones de macros con parámetros no solo puede completar la función de llamada de función, sino también evitar las operaciones de pila y pila de la función, reducir la sobrecarga del sistema y mejorar la eficiencia operativa;
    3. La macro es procesada por el preprocesador Muchas funciones que el compilador no puede lograr pueden lograrse mediante la manipulación de cadenas. Como conector ##.

Desventajas:

  1. Debido a que está incrustado directamente, el código puede ser relativamente más;
  2. Demasiadas definiciones anidadas pueden afectar la legibilidad del programa y es fácil cometer errores;
  3. Para macros con parámetros, dado que es un reemplazo directo, no verifica si los parámetros son legales, lo que representa un riesgo de seguridad.
  4. ¿Cómo accede la CPU a la memoria?
    Primero
    observe un diagrama de flujo simple de la CPU accediendo a la memoria a través de la unidad de administración de memoria (MMU) :
    Inserte la descripción de la imagen aquí

TLB: Convierta la caché lookaside. Con ella, la velocidad de conversión de la dirección virtual a la dirección física se puede incrementar considerablemente.
De la figura anterior, podemos conocer claramente la relación entre CPU, DDR y MMU. Cuando se enciende la MMU, la CPU accede a todas las direcciones virtuales.
Primero, la dirección virtual se convierte en una dirección física a través de la MMU,
y luego se accede a la memoria a través del bus (todos sabemos que la memoria está colgada en el bus).
Entonces, ¿cómo convierte la MMU la dirección virtual en la dirección física? Por supuesto, es a través de la tabla de páginas. La MMU descubre la dirección física correspondiente a la dirección virtual de la tabla de páginas y luego accede a la memoria física.

Supongo que te gusta

Origin blog.csdn.net/lingshengxueyuan/article/details/108602597
Recomendado
Clasificación