Perguntas e respostas da entrevista selecionada em 2020 (1)

1. Na competição ACM, uma equipe é composta por três jogadores. Agora há N + M alunos, dos quais N alunos são bons em algoritmos, e os alunos M restantes são bons em programação. Esses alunos devem participar da competição ACM. Seu treinador Cada equipe deve ter pelo menos um aluno que seja bom em algoritmos e um aluno que seja bom em programação. Quantas equipes esses alunos podem formar?

Entrada: Insira dois inteiros M e N, onde 1 <N, M <10000000
Saída: o número máximo de equipes que podem ser formadas

#include <iostream>
using namespace std;
 
int main()
{
    
    
    int cnt = 0,n,m;
    cout << "输入N个擅长算法的,M个擅长编程的:" << endl;
    cin >> n >> m;
    while(n!=0&&m!=0&&m+n!=2){
    
    
        if(n>=m){
    
    
            n = n-2;
            m = m-1;
            cnt++;
        }
        else if(n<m){
    
    
            m = m-2;
            n = n-1;
            cnt++;
        }
    }
    cout << "最大组对数量" << cnt << endl;
    return 0;
}

2. O que é idempotência

O conceito de idempotência: Em termos gerais, idempotência significa que não importa quantas operações repetidas sejam realizadas, o mesmo resultado é alcançado.

3. O que são operações idempotentes em solicitações REST

GET, PUT e DELETE são operações idempotentes, enquanto POST não é uma
análise.
Em primeiro lugar, as solicitações GET são bem compreendidas. Se você consultar recursos várias vezes, os resultados dessa implementação serão os mesmos.
A idempotência da solicitação PUT pode ser entendida desta forma. Modificando A para B, seu primeiro valor de solicitação passa a ser B, e depois realizando esta operação muitas vezes, o resultado final ainda é B, que é o mesmo que o resultado de uma execução, então PUT É uma operação idempotente.
Da mesma forma, a operação DELETE pode ser entendida, após a exclusão do recurso pela primeira vez, a solicitação de exclusão é feita várias vezes posteriormente, e o resultado final é o mesmo, e o recurso é excluído.
POST não é uma operação idempotente, porque uma solicitação adiciona um novo recurso e uma segunda solicitação adiciona dois novos recursos. Várias solicitações produzirão resultados diferentes, portanto, POST não é uma operação idempotente.

4. Distinguir o uso de POST e PUT com base na idempotência

Pode ser distinguido de acordo com idempotente (idempotente).
Para dar um exemplo simples, se houver um sistema de blog que fornece uma API da Web, o modo é como este http: // superblogging / blogs / {blog-name}, muito simples, substitua {blog-name} pelo nome do nosso blog, vá para Este URL envia uma solicitação HTTP PUT ou POST. A parte do corpo do HTTP é a postagem do blog. Este é um exemplo de API REST muito simples.
Devemos usar o método PUT ou o método POST?
Depende se o comportamento desse serviço REST é idempotente, se enviarmos duas solicitações http: // superblogging / blogs / post / Sample, que tipo de comportamento existe no lado do servidor? Se duas postagens de blog forem geradas, significa que esse serviço não é idempotente, porque o uso múltiplo tem efeitos colaterais; se a primeira solicitação for substituída pela última solicitação, o serviço será idempotente. No primeiro caso, o método POST deve ser usado e, no último caso, o método PUT deve ser usado.

5. As deficiências do CAS e suas soluções.

As deficiências do CAS são como problema ABA, problema de consumo de spin lock, problema de consistência de compartilhamento multivariável
1.ABA:
Descrição do problema: Thread t1 muda seu valor de A para B, e então de B para A. Ao mesmo tempo, um segmento t2 deseja alterar o valor de A para C. Mas quando o CAS verifica, ele descobrirá que não há mudança, mas na verdade ele mudou. Pode causar perda de dados.
Solução: CAS ainda é semelhante ao bloqueio otimista, adicionando um número de versão ou carimbo de data / hora a ele da mesma maneira que o bloqueio otimista de dados, como AtomicStampedReference
2. Spin consome recursos:
Descrição do problema: Quando vários threads competem pelo mesmo recurso, se spin Se não obtiver sucesso, a CPU estará sempre ocupada.
Solução: Destrua o loop for infinito. Quando um determinado tempo ou número de vezes for excedido, o retorno será encerrado. O LongAddr adicionado no JDK8 é semelhante ao ConcurrentHashMap. Quando vários encadeamentos competem, a granularidade é reduzida e uma variável é dividida em várias variáveis ​​para obter o efeito de vários encadeamentos acessando vários recursos e, finalmente, sum é chamado para combiná-los.
Embora a base e as células sejam modificadas por volatilidade, parece que a operação de soma não está bloqueada e o resultado da soma pode não ser tão preciso.
2. Problema de consistência de compartilhamento multivariável:
Solução: a operação CAS é para uma variável, se você operar em várias variáveis,

  1. Isso pode ser resolvido com um bloqueio.
  2. Empacotado em classes de objetos para resolver.
    Insira a descrição da imagem aqui
    (Mais C / C ++ gratuito, Linux, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP / IP, corrotina, DPDK, etc. vários pontos de conhecimento produtos secos Materiais de aprendizagem mais grupo 960994558)

6. Recursos da árvore B +

(1) O número de palavras-chave de cada nó é igual ao número de filhos. As palavras-chave de todos os nós internos não inferiores são as maiores na subárvore correspondente, e os nós internos inferiores contêm todas as palavras-chave .
(2) Exceto para o nó raiz, cada nó interno tem até m filhos. [3]
(3) Todos os nós folha estão no mesmo nível da estrutura da árvore e não contêm nenhuma informação (podem ser considerados nós externos ou nós que falham na pesquisa). Portanto, a estrutura da árvore está sempre equilibrada na altura da árvore.

7. Explique em detalhes o isolamento das transações.

Isolamento de
transações Para garantir o isolamento de transações, podemos naturalmente projetar transações para serem single-threaded. Desta forma, a eficiência será extremamente baixa. Para garantir o isolamento sem perder eficiência, dividimos a perda de isolamento em três situações .
Leitura suja: lê os dados de outra transação não confirmada.
Leitura fantasma: A tabela foi lida uma vez durante uma transação. Neste momento, outra confirmação de transação acontece para fazer com que a transação leia a tabela novamente de forma inconsistente. (Impacto da tabela)
Leitura não repetível: Os dados de a foram lidos uma vez durante uma transação e outra transação é confirmada neste momento, o que faz com que a transação leia os dados de novamente de forma inconsistente.
Quatro níveis de isolamento são introduzidos para essas três situações.
Quatro níveis de isolamento:
leitura não confirmada - não evita problemas de isolamento, com problemas de leitura suja / não repetibilidade / leitura fantasma (leitura fantasma)
Leitura confirmada - pode evitar problemas de leitura suja, Mas não pode evitar problemas de não repetibilidade / leitura fantasma (leitura fantasma) Leitura
repetível - pode evitar problemas de leitura suja / leitura não repetível, mas não pode prevenir problemas de leitura virtual (leitura fantasma)
Serializável - o banco de dados é projetado como um banco de dados de thread único, o que pode evitar o acima Todos os problemas
Esses quatro níveis de isolamento aumentam a segurança. Diminuindo a eficiência

8. Sabendo que uma função rand7 () pode gerar números aleatórios de 1-7, forneça uma função que possa gerar números aleatórios de 1-10. A solução é baseada em um método chamado amostragem de rejeição. A ideia principal é que, enquanto um número aleatório dentro do intervalo de destino for gerado, ele será retornado diretamente. Se o número aleatório gerado não estiver dentro do intervalo de destino, descarte o valor e faça uma nova amostra. Uma vez que os números no intervalo de destino são selecionados com probabilidade igual, tal distribuição uniforme é gerada.

Obviamente, rand7 precisa ser executado pelo menos duas vezes, caso contrário, o número 1-10 não será gerado. Executando rand7 duas vezes, inteiros de 1-49 podem ser gerados,
Insira a descrição da imagem aqui

Uma vez que 49 não é um múltiplo de 10, precisamos descartar alguns valores. O intervalo de números que queremos é de 1 a 40. Se não estiver neste intervalo, descarte e faça uma nova amostra.

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;
}

Como o intervalo de linhas é 1-7 e o intervalo col é 1-7, o intervalo de valores idx é 1-49. Valores maiores que 40 são descartados, de forma que os números restantes no intervalo de 1 a 40 sejam retornados por módulo. Vamos calcular o valor esperado do número de vezes de amostragem que atende ao intervalo de 1-40:
Insira a descrição da imagem aqui

9. Explique quais medidas de otimização estão disponíveis para o pool de threads.

Quanto maior for a proporção do tempo de espera do thread, mais threads serão necessários. Quanto maior a proporção de tempo de CPU do thread, menos threads são necessários.
Se você usa uma operação intensiva de CPU, o número de threads é igual ao número de núcleos de CPU, evitando muitos contextos de thread de comutação inúteis.
Se você usa muito IO e precisa de muita espera, o número de threads pode ser definido mais, por exemplo Multiplique os núcleos da CPU por 2.

10. C ++ 11 três maneiras de criar threads

  1. Através do
    thread de função :
    junção de classe de biblioteca padrão : bloquear o thread principal e 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 (): separe completamente o thread principal e o thread filho, o thread filho residirá em segundo plano e será executado e será assumido pela biblioteca de tempo de execução C ++ e perderá o controle

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 (): determine se você pode usar join () ou detach () com sucesso

Descrição do programa: Join não pode ser implementado após desanexar

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. Criar thread por meio de objeto de classe

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;
}

O objeto é destruído quando o thread principal termina com detach (). As funções de membro dos threads filhos podem ser chamadas?
O objeto aqui será copiado para o thread filho. Quando o thread principal terminar, o objeto do thread filho copiado não será destruído.
Enquanto não houver referência, o ponteiro não causará problemas.

Verifique se o objeto é copiado para o thread filho, copiando o construtor e o destruidor

// 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;
}

O destruidor do thread filho é executado em segundo plano, portanto, o dtor de saída é o thread principal. O resultado do uso de join () é:

3. Crie threads por meio de expressões lambda

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

É melhor aplicar o que você aprendeu, e pode haver deficiências no acima, bem-vindo para apontar a discussão.

Acho que você gosta

Origin blog.csdn.net/weixin_52622200/article/details/110440419
Recomendado
Clasificación