멀티 스레딩 동기화 인터뷰 질문 : m 개의 스레드에서 n 개의 단어 찾기


23

이 문제가 단일 스레드가 아닌 여러 스레드가있는 솔루션에서 이점을 얻을 수있는 방법이 있습니까?


인터뷰에서 여러 스레드를 사용하여 문제를 해결하라는 요청을 받았습니다. 여러 스레드가 도움이되지 않는 것으로 보입니다.

문제는 다음과 같습니다.

n 개의 단어를 포함하는 단락이 주어지며 m 개의 스레드가 제공됩니다. 당신이해야 할 일은 각 스레드가 한 단어를 인쇄하고 다음 스레드에 제어를 제공해야한다는 것입니다.이 방법으로 각 스레드는 한 단어를 계속 인쇄합니다. 마지막 스레드가 오는 경우 첫 번째 스레드를 호출해야합니다. 단락에 모든 단어가 인쇄 될 때까지 인쇄가 반복됩니다. 마지막으로 모든 스레드가 정상적으로 종료되어야합니다. 어떤 종류의 동기화가 사용됩니까?

나는 우리가 여기서 스레드를 활용할 수는 없다고 생각하지만, 인터뷰자는 내 동기화 기술을 측정하려고한다고 생각합니다. 이 문제에서 여러 스레드가 가치를 가질 수있는 것을 놓치고 있습니까?

코드가 필요하지 않습니다. 생각 만하세요. 나는 스스로 구현할 것이다.


C ++ 태그를 추가하면 도움이되지 않을 것입니다. 여기에있는이 질문들은 특정 언어를 능가하는보다 개념적적인 것입니다.
cHao

당신의 감정을 신뢰하십시오. 나는 그들이 가고있는 것을 이해하지만, 난 당신이 어떻게에서 지금까지 이탈 인터뷰 질문 좋아 한 적이 있어야 현실 세계의 문제를 해결합니다.
G_P

16
@rplusg-솔루션이 문제를 직렬화하고 실제로 동시 처리를 수행하지 않고 스레드 오버 헤드를 추가한다는 점을 지적한 인터뷰 대상자는 훨씬 더 감명을 받았습니다. 면접관은 항상 질문에 따라 질문에 답변 할 것을 요구할 수 있습니다.
David Harkness

"각 스레드가 한 단어를 인쇄하고 다음 스레드에 제어를 제공해야하는 경우"는 직렬 작업처럼 들립니다. 즉, 한 스레드가 이전 스레드가 완료되기를 기다리고 있으며 릴레이를 전달하는 것과 같습니다. 그렇다면 왜 단일 스레드 응용 프로그램으로 만들지 않겠습니까?
amphibient

1
나는 그것을 @Blrfl로 얻는다. 그것은 도구 X를 사용하는 방법을 알고 있는지 확인해야하지만 실제로 그 도구의 사용을 보증하는 실제 응용 프로그램 사용 사례 시나리오를 설계하기에는 너무 게 으르거나 조잡했습니다. 그것에 슬프게. 솔직히, 내가 인터뷰에서 그 질문을 받았다면, 나는 그에게 전화를 걸었을 것이고 아마도 어리석은 사람과 같은 사람과 일하고 싶지 않을 것입니다
양서류

답변:


22

그들이 세마포어 솔루션으로 당신을 이끌고있는 것처럼 들립니다. 세마포어는 다른 스레드가 자신의 차례임을 알리는 데 사용됩니다. 그들은 뮤텍스보다 훨씬 덜 자주 사용되기 때문에 좋은 인터뷰 질문이라고 생각합니다. 또한 예제가 고안된 것 같습니다.

기본적으로 m세마포어를 만듭니다 . 각 스레드 x는 세마포어 x를 기다린 다음 x+1작업을 수행 한 후 세마포어에 게시합니다 . 의사 코드에서 :

loop:
    wait(semaphore[x])
    if no more words:
        post(semaphore[(x+1) % m])
        exit
    print word
    increment current word pointer
    post(semaphore[(x+1) % m])

현상금에 감사드립니다. 마우스로 그것을 준 사람이 말할 것임을 알아 내기 위해 시간이 걸렸습니다.
kdgregory

내 무지를 실례합니다.이 솔루션이 어떻게 정확한지 더 자세히 설명해 주시겠습니까? 이것은 새로운 멋진 유형의 세마포어입니까? 그러나 질문은 [세마포어가 사용하는] 대기 / 알림 솔루션으로 해결 될 것이라고 확신합니다.
AJed

표준 세마포어 배열 일뿐입니다. 그들에게는 특별한 것이 없습니다. 일부 구현에서는 알림을 "게시"라고합니다.
Karl Bielefeldt

@KarlBielefeldt 글쎄, 만약 모든 쓰레드 x가 세마포어 x를 기다린다면, 모든 쓰레드가 차단되고 아무 일도 일어나지 않을 것이다. 만약 wait (sem)이 실제로 획득 (sem)이라면, 그들은 동시에 들어오고 배제하지 않습니다. 더 많은 설명이있을 때 까지이 의사 코드에 문제가 있다고 생각하며 최선의 대답이되어서는 안됩니다.
AJed

이것은 각 스레드의 루프를 보여줍니다. 설정 코드는 작업을 시작하기 위해 첫 번째 세마포어에 게시해야합니다.
Karl Bielefeldt

23

제 생각에는 이것은 (1) 응시자가 스레딩에 대한 깊은 지식을 가지고 있고, (2) 면접관도 깊은 지식을 가지고 있으며 후보자를 조사하기 위해 질문을 사용한다고 가정 할 때 멋진 인터뷰 질문입니다. 면접관은 구체적이고 좁은 답변을 찾고 있었지만 유능한 면접관은 다음을 찾아야합니다.

  • 구체적인 개념과 구체적인 구현을 구별하는 능력. 나는이 의견을 주로 일부 의견에 대한 메타 의견으로 던졌습니다. 아니요, 이런 식으로 단일 단어 목록을 처리하는 것은 이치에 맞지 않습니다. 그러나 기능이 다른 여러 시스템에 걸쳐있을 수있는 작업 파이프 라인의 추상 개념이 중요합니다.
  • 저의 경험 (약 30 년의 분산, 다중 프로세스 및 다중 스레드 응용 프로그램)에서 작업을 배포하는 것은 어려운 일이 아닙니다. 결과를 수집하고 독립적 인 프로세스를 조정 하는 것은 대부분의 스레딩 버그가 발생하는 곳입니다 (제 경험상). 면접관은 문제를 간단한 체인으로 분류하여 응시자가 조정에 대해 얼마나 잘 생각하는지 확인할 수 있습니다. 또한 인터뷰 담당자는 "확인, 각 스레드가 재구성을 위해 다른 스레드에 단어를 보내야하는 경우"와 같은 모든 종류의 후속 질문을 할 수 있습니다.
  • 응시자는 프로세서의 메모리 모델이 구현에 어떤 영향을 줄 수 있는지 생각합니까? 한 작업의 결과가 L1 캐시에서 플러시되지 않으면 명백한 동시성이없는 경우에도 버그입니다.
  • 응시자는 스레딩을 응용 프로그램 논리와 분리합니까?

이 마지막 요점은 제 생각에 가장 중요합니다. 다시 한 번, 내 경험에 따르면 스레딩이 응용 프로그램 논리와 혼합되어 있으면 스레드 코드를 디버깅하는 것이 기하 급수적으로 어려워집니다 (예를 들어 SO의 모든 스윙 질문을 살펴보십시오). 최고의 멀티 스레드 코드는 명확하게 정의 된 핸드 오프와 함께 독립적 인 단일 스레드 코드로 작성되었다고 생각합니다.

이것을 염두에두고, 내 접근 방식은 각 스레드에 두 개의 대기열을 제공하는 것입니다. 하나는 입력, 하나는 출력입니다. 입력 큐를 읽는 동안 스레드가 차단되고 문자열에서 첫 번째 단어를 빼고 나머지 문자열을 출력 큐로 전달합니다. 이 방법의 일부 기능은 다음과 같습니다.

  • 애플리케이션 코드는 큐를 읽고 데이터에 대한 작업을 수행하고 큐를 작성합니다. 멀티 스레드인지 아닌지 또는 대기열이 한 시스템의 메모리 내 대기열인지 아니면 세계 반대편에있는 시스템 간의 TCP 기반 대기열인지는 상관하지 않습니다.
  • 응용 프로그램 코드는 단일 스레드로 작성되므로 많은 스캐 폴딩 없이도 결정적인 방식으로 테스트 할 수 있습니다.
  • 실행 단계 동안 애플리케이션 코드는 처리중인 문자열을 소유합니다. 동시 실행 스레드와의 동기화에 신경 쓸 필요가 없습니다.

즉, 유능한 면접관이 조사 할 수있는 많은 회색 영역이 여전히 있습니다.

  • "하지만, 동시성 프리미티브에 대한 지식을 찾고 있습니다. 차단 대기열을 구현할 수 있습니까?" 물론 첫 번째 대답은 선택한 플랫폼에서 사전 구축 된 차단 대기열을 사용해야한다는 것입니다. 그러나 스레드를 이해하면 플랫폼에서 지원하는 동기화 기본 요소를 사용하여 수십 줄의 코드로 큐 구현을 작성할 수 있습니다.
  • "프로세스의 한 단계가 매우 오랜 시간이 걸리면 어떻게해야합니까?" 제한 또는 제한되지 않은 출력 대기열을 원하는지, 오류를 처리하는 방법 및 지연이있을 경우 전체 처리량에 미치는 영향을 고려해야합니다.
  • 소스 문자열을 효율적으로 큐에 넣는 방법 인 메모리 대기열을 처리하는 경우 반드시 문제는 아니지만 시스템간에 이동하는 경우 문제가 될 수 있습니다. 또한 변경 불가능한 기본 바이트 배열 위에서 읽기 전용 래퍼를 탐색 할 수도 있습니다.

마지막으로, 동시 프로그래밍 경험이 있다면이 모델을 이미 따르는 일부 프레임 워크 (예 : Akka for Java / Scala)에 대해 이야기 할 수 있습니다.


프로세서의 L1 캐시에 대한 그 전체 노트가 정말 흥미로웠다. 투표함.
Marc DiMillo

나는 최근 Spring 5와 함께 projectReactor를 사용했다. 이것은 스레드와 무관 한 코드를 작성할 수있게한다.
Kundan 보라

16

면접 질문은 실제로 까다로운 질문으로, 해결하려는 문제에 대해 생각 하게합니다 . 질문에 대한 질문은 실제 상황이든 인터뷰이든 관계없이 모든 문제 에 접근 하는 데 필수적인 부분입니다 . 기술 인터뷰에서 질문에 접근하는 방법에 대해 인터넷을 유포하는 많은 비디오가 있습니다 (특히 Google 및 Microsoft에 대해 살펴보십시오).

"그냥 대답하려고 노력하고 거기서 나가십시오."

이 사고 패턴으로 인터뷰에 접근하면 일할 가치가있는 회사에 대한 인터뷰를 폭파하게됩니다.

당신이 많은 것을 얻는다고 생각하지 않는다면 (스레딩에서 무언가가 있다면) 그들에게 말하십시오. 혜택이 없다고 생각 하는지 알려주십시오 . 그들과 토론하십시오. 기술 인터뷰는 공개 토론 플랫폼입니다. 유용 할 있는 방법에 대해 배우게 될 수도 있습니다. 면접관이 말한 것을 맹목적으로 구현하려고하지 마십시오.


3
요청 된 질문에 대한 답변을 제공하지 않기 때문에이 답변을 하향 투표했습니다.
Robert Harvey

1
@RobertHarvey : 때때로 사람들은 잘못된 질문을 합니다. OP는 기술 인터뷰를 다루기에는 열악한 사고 방식을 가지고 있으며,이 답변은 그를 올바른 길로 인도하기위한 시도였습니다.
데미안 브레히트

1
@RobertHarvey 솔직히 이것이 이것이 질문에 대한 정답이라고 믿습니다. 여기서 키워드는 질문의 제목과 본문에 언급 된 "인터뷰 질문"입니다. 그러한 질문에 대해서는 이것이 정답입니다. 질문이 "나는 m 개의 스레드와 n 단어의 단락을 가지고 있으며, 이것과 함께하고 싶을 때 더 나은 방법은 무엇입니까?"라면이 대답은 질문에 적합하지 않았을 것입니다. 나는 그것이 훌륭하다고 생각합니다. 역설 : 나는 여기에 주어진 조언을 따르지 않았기 때문에 인터뷰 질문을 폭파했다
Shivan Dragon

@RobertHarvey 그것은 관련 질문에 대답하고, 하향 투표는 아무것도 달성하지 못했습니다.
Marc DiMillo 2013

0
  • 먼저 단락을 적절한 구분 기호로 토큰 화하고 단어를 대기열에 추가하십시오.

  • N 개의 스레드를 작성하고 스레드 풀에 보관하십시오.

  • 스레드 풀을 반복하고 스레드를 시작하고
    스레드가 조인 될 때까지 기다리십시오 . 그리고 첫 번째 스레드가 끝나면 다음 스레드를 시작하십시오.

  • 각 스레드는 대기열을 폴링하고 인쇄해야합니다.

  • 스레드 풀 내에서 모든 스레드가 사용되면 풀의 시작 부분부터 시작하십시오.


0

당신이 말했듯이, 나는이 시나리오가 스레딩에서 크게 이익을 얻지 못한다고 생각합니다. 단일 스레드 구현보다 속도가 느릴 수 있습니다.

그러나 내 대답은 단어 배열 인덱스에 대한 액세스를 제어하는 ​​잠금에 액세스하려고 각 루프를 단단한 루프로 만드는 것입니다. 각 스레드는 잠금을 잡고 인덱스를 가져오고 배열에서 해당 단어를 가져 와서 인쇄하고 인덱스를 증가시킨 다음 잠금을 해제합니다. 인덱스가 배열의 끝에 있으면 스레드가 종료됩니다.

이 같은:

while(true)
{
    lock(index)
    {
        if(index >= array.length())
          break;
        Console.WriteLine(array[index]);
        index++;
    }
}

이것이 다른 요구 사항에 따라 하나의 스레드를 달성해야한다고 생각하지만 스레드의 순서는 보장되지 않습니다. 다른 솔루션도 듣고 싶습니다.


-1

이 문제를 해결하려면 조건 대기 / 신호 API를 사용하십시오.

첫 번째 스레드가 1 워드를 선택하고 나머지 스레드가 신호를 기다리고 있다고 가정 해 봅시다. 첫 번째 스레드는 첫 번째 단어를 인쇄하고 다음 스레드로 신호를 생성 한 다음 두 번째 스레드는 두 번째 단어를 인쇄하고 세 번째 스레드로 신호를 생성합니다.

#include <iostream>
#include <fstream>
#include <pthread.h>
#include <signal.h>
pthread_cond_t cond[5] = {PTHREAD_COND_INITIALIZER,};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

using namespace std;

string gstr;

void* thread1(void*)
{
    do {
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond[0],&mutex);
    cout <<"thread1 :"<<gstr<<endl;
    pthread_mutex_unlock(&mutex);
    }while(1);
}

void* thread2(void*)
{
    do{
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond[1],&mutex);
    cout <<"thread2 :"<<gstr<<endl;
    pthread_mutex_unlock(&mutex);
    }while(1);
}

void* thread3(void*)
{
    do{
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond[2],&mutex);
    cout <<"thread3 :"<<gstr<<endl;
    pthread_mutex_unlock(&mutex);
    }while(1);
}

void* thread4(void*)
{
    do{
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond[3],&mutex);
    cout <<"thread4 :"<<gstr<<endl;
    pthread_mutex_unlock(&mutex);
    }while(1);
}

void* thread5(void*)
{
    do{
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond[4],&mutex);
    cout <<"thread5 :"<<gstr<<endl;
    pthread_mutex_unlock(&mutex);
    }while(1);
}

int main()
{
    pthread_t t[5];
    void* (*fun[5])(void*);
    fun[0]=thread1;
    fun[1]=thread2;
    fun[2]=thread3;
    fun[3]=thread4;
    fun[4]=thread5;

    for (int i =0 ; i < 5; ++i)
    {
        pthread_create(&t[i],NULL,fun[i],NULL);
    }
    ifstream in;
    in.open("paragraph.txt");
    int i=0;
    while(in >> gstr)
    {

        pthread_cond_signal(&cond[i++]);
        if(i == 5)
            i=0;
        usleep(10);
    }
    for (int i =0 ; i < 5; ++i)
    {
        int ret = pthread_cancel(t[i]);
        if(ret != 0)
            perror("pthread_cancel:");
        else
            cout <<"canceled\n";
    }
    pthread_exit(NULL);
}

-1

[여기에 사용 된 용어는 POSIX 스레드와 관련이있을 수 있습니다]

이 문제를 해결하기 위해 FIFO 뮤텍스를 사용할 수 있어야합니다.

사용처 :

두 개의 스레드 T1과 T2가 임계 섹션을 실행하려고한다고 가정하십시오. 둘 다이 중요한 부분을 벗어나서 많은 시간을 할애 할 필요가 없습니다. 따라서, T1은 잠금, 실행 및 잠금 해제 및 웨이크 업을 위해 T2에 신호를 보낼 수있다. 그러나 T2가 깨어나 잠금을 획득하기 전에 T1은 잠금을 다시 획득하고 실행합니다. 이런 식으로, T2가 실제로 잠금을 획득하기 전에 아주 오래 기다려야 할 수도 있습니다.

작동 방식 / 구현 방법 :

뮤텍스를 잠그십시오. 각 스레드의 스레드 특정 데이터 (TSD)를 스레드 ID 및 세마포어를 포함하는 노드로 초기화하십시오. 또한 소유 (TRUE 또는 FALSE 또는 -1), 소유자 (소유자 스레드 ID)의 두 변수가 있습니다. 또한 웨이터 큐의 마지막 노드를 가리키는 웨이터 큐 및 포인터 waiterLast를 유지하십시오.

잠금 조작 :

node = get_thread_specific_data(node_key);
lock(mutex);
    if(!owned)
    {
        owned = true;
        owner = self;
        return success;
    }

    node->next = nullptr;
    if(waiters_queue == null) waiters_queue = node;
    else waiters_last->next = node;

    waiters_last = node;
unlock(mutex);
sem_wait(node->semaphore);

lock(mutex);
    if(owned != -1) abort();
    owned = true;
    owner = self;
    waiters_queue = waiters_queue->next;
 unlock(mutex);

잠금 해제 조작 :

lock(mutex);
    owner = null;
    if(waiters_queue == null)
    {
        owned = false;
        return success;
    }
    owned = -1;
    sem_post(waiters_queue->semaphore);
unlock(mutex);

-1

흥미로운 질문입니다. 다음은 스레드 간 랑데부 채널을 만들기 위해 SynchronousQueue를 사용하는 Java의 솔루션입니다.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.SynchronousQueue;

public class FindNWordsGivenMThreads {

    private static final int NUMBER_OF_WORDS = 100;
    private static final int NUMBER_OF_THREADS = 5;
    private static final Stack<String> POISON_PILL = new Stack<String>();

    public static void main(String[] args) throws Exception {
        new FindNWordsGivenMThreads().run();
    }

    private void run() throws Exception {
        final Stack<String> words = loadWords();
        SynchronousQueue<Stack<String>> init = new SynchronousQueue<Stack<String>>();
        createProcessors(init);
        init.put(words);
    }

    private void createProcessors(SynchronousQueue<Stack<String>> init) {
        List<Processor> processors = new ArrayList<Processor>();

        for (int i = 0; i < NUMBER_OF_THREADS; i++) {

            SynchronousQueue in;
            SynchronousQueue out;

            if (i == 0) {
                in = init;
            } else {
                in = processors.get(i - 1).getOut();
            }

            if (i == (NUMBER_OF_THREADS - 1)) {
                out = init;
            } else {
                out = new SynchronousQueue();
            }

            Processor processor = new Processor("Thread-" + i, in, out);
            processors.add(processor);
            processor.start();

        }

    }

    class Processor extends Thread {

        private SynchronousQueue<Stack<String>> in;
        private SynchronousQueue<Stack<String>> out;

        Processor(String name, SynchronousQueue in, SynchronousQueue out) {
            super(name);
            this.in = in;
            this.out = out;
        }

        @Override
        public void run() {

            while (true) {

                Stack<String> stack = null;
                try {
                    stack = in.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (stack.empty() || stack == POISON_PILL) {
                    System.out.println(Thread.currentThread().getName() + " Done!");
                    out.offer(POISON_PILL);
                    break;
                }

                System.out.println(Thread.currentThread().getName() + " " + stack.pop());
                out.offer(stack);
            }
        }

        public SynchronousQueue getOut() {
            return out;
        }
    }

    private Stack<String> loadWords() throws Exception {

        Stack<String> words = new Stack<String>();

        BufferedReader reader = new BufferedReader(new FileReader(new File("/usr/share/dict/words")));
        String line;
        while ((line = reader.readLine()) != null) {
            words.push(line);
            if (words.size() == NUMBER_OF_WORDS) {
                break;
            }
        }
        return words;
    }
}

-2

나는 이런 종류의 질문은 대답하기가 매우 어렵다고 말하고 싶습니다. 왜냐하면 그것은 완전히 어리석은 일을하는 가장 좋은 방법을 요구하기 때문입니다. 내 뇌는 그렇게 작동하지 않습니다. 어리석은 질문에 대한 해결책을 찾을 수 없습니다. 내 두뇌는 이러한 조건에서 여러 스레드를 사용하는 것이 의미가 없으므로 즉시 단일 스레드를 사용한다고 말합니다.

그런 다음 스레딩에 대한 실제 질문을하거나 심각한 스레딩에 대한 실제 예제를 제공하도록 요청했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.