マルチスレッド同期のインタビューの質問:m個のスレッドからn個の単語を見つける


23

この問題は、単一のスレッドではなく、複数のスレッドを使用したソリューションの恩恵を受けることができますか?


インタビューで、複数のスレッドを使用して問題を解決するように依頼されました。私には、複数のスレッドは何の利益ももたらさないように見えます。

問題は次のとおりです。

n個の単語を含む段落が与えられ、m個のスレッドが与えられます。あなたがする必要があるのは、各スレッドが1つの単語を印刷し、次のスレッドに制御を与える必要があることです。このように、各スレッドは1つの単語を印刷し続け、最後のスレッドが来た場合、最初のスレッドを呼び出す必要があります。すべての単語が段落に印刷されるまで、印刷が繰り返されます。最後に、すべてのスレッドが正常に終了する必要があります。どのような同期を使用しますか?

ここではスレッドを活用できないと強く感じていますが、インタビュアーは私の同期スキルを測定しようとしていると考えています。この問題で、複数のスレッドに価値をもたらす何かが欠けていますか?

コードは不要です。考えてみてください。自分で実装します。


C ++タグを追加しても、おそらくここではあまり役に立ちません。ここにあるこの質問は、特定の言語を超越するより概念的なものです。
cHao

あなたの気持ちを信頼してください。私は彼らが何を目指しているのか理解しています、実際の問題をどのように解決すべきかという点から、これまでに逸脱したインタビューの質問が好きではありませんでした。
G_P

16
@rplusg-ソリューションが問題をシリアル化し、実際に同時処理を行わずにスレッドのオーバーヘッドを追加するだけだと指摘したインタビューを受けた人には、もっと感銘を受けるでしょう。インタビュアーは、あなたが尋ねられた質問にいつでも答えることを主張できます。
デビッドハークネス

「各スレッドが1つの単語を出力し、次のスレッドに制御を与える」場合、それは連続作業のように聞こえます。その場合、なぜシングルスレッドのアプリにしないのですか?
両生類

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

私の無知を失礼しますが、この解決策がどのように正しいかについて詳しく説明していただけますか?これはいくつかの新しい派手なセマフォですか?ただし、この質問は待機/通知ソリューション(セマフォが使用)によって解決されると確信しています。
2014

これは単なる標準セマフォの配列です。それらについて特別なことは何もありません。通知は、一部の実装では「ポスト」と呼ばれます。
カールビーレフェルト

@KarlBielefeldtさて、すべてのスレッドxがセマフォxを待機する場合、すべてのスレッドがブロックされ、何も起こりません。wait(sem)が実際にacquire(sem)である場合、それらは同時に取得され、除外はありません。詳細が明らかになるまで、この擬似コードに何か問題があると信じており、それが最良の答えではないはずです。
2014

これは、各スレッドのループを示しています。セットアップコードは、物事を開始するために最初のセマフォにポストする必要があります。
カールビーレフェルト

23

私の意見では、これは素晴らしいインタビューの質問です-少なくとも(1)候補者はスレッド化の深い知識を持っていることが期待され、(2)インタビュアーも深い知識を持ち、質問を使用して候補者を調査します。インタビュアーが特定の狭い回答を探している可能性は常にありますが、有能なインタビュアーは以下を探す必要があります。

  • 抽象的な概念と具体的な実装を区別する機能。私はこれを主にいくつかのコメントのメタコメントとして投入します。いいえ、単一の単語リストをこの方法で処理することは意味がありません。ただし、機能の異なる複数のマシンにまたがる可能性がある操作のパイプラインの抽象的な概念は重要です。
  • 私の経験(ほぼ30年間の分散、マルチプロセス、マルチスレッドアプリケーション)では、作業の分散は難しい部分ではありません。結果を収集し、独立したプロセス調整することは、ほとんどのスレッド化バグが発生する場所です(これも私の経験では)。問題を単純なチェーンに抽出することにより、面接官は候補者が調整についてどれだけよく考えているかを確認できます。さらに、インタビュアーは、「OK、各スレッドが再構築のために別のスレッドに単語を送信しなければならない場合」など、あらゆる種類の後続の質問をする機会があります。
  • 候補者は、プロセッサのメモリモデルが実装にどのように影響するかについて考えますか?1つの操作の結果がL1キャッシュからフラッシュされない場合、明らかな並行性がなくてもそれはバグです。
  • 候補者は、スレッドをアプリケーションロジックから分離していますか?

私の意見では、この最後の点が最も重要です。繰り返しますが、私の経験に基づいて、スレッド化とアプリケーションロジックが混在していると、スレッド化されたコードをデバッグするのが指数関数的に難しくなります(SOのSwingに関するすべての質問を見てください)。最高のマルチスレッドコードは、明確に定義されたハンドオフを備えた自己完結型のシングルスレッドコードとして記述されていると思います。

これを念頭に置いて、私のアプローチは、各スレッドに2つのキューを与えることです。1つは入力用、もう1つは出力用です。スレッドは、入力キューの読み取り中にブロックし、文字列から最初の単語を取り出し、文字列の残りの部分を出力キューに渡します。このアプローチの機能の一部:

  • アプリケーションコードは、キューの読み取り、データに対する処理、およびキューの書き込みを行います。マルチスレッドであるかどうか、またはキューが1つのマシンのメモリ内キューであるか、世界の反対側にあるマシン間のTCPベースのキューであるかは関係ありません。
  • アプリケーションコードはシングルスレッドのように記述されているため、多くの足場を必要とせずに決定論的にテストできます。
  • 実行フェーズでは、アプリケーションコードが処理中の文字列を所有します。同時実行スレッドとの同期を気にする必要はありません。

とはいえ、有能なインタビュアーが調査できる灰色の領域はまだたくさんあります。

  • 「OK。でも、同時実行プリミティブに関する知識を探しています。ブロッキングキューを実装できますか?」もちろん、最初の答えは、選択したプラットフォームから事前に作成されたブロッキングキューを使用することです。ただし、スレッドを理解している場合は、プラットフォームでサポートされている同期プリミティブを使用して、数十行のコードでキュー実装を作成できます。
  • 「プロセスの1つのステップに非常に長い時間がかかる場合はどうなりますか?」制限付きまたは制限なしの出力キューが必要かどうか、エラーの処理方法、および遅延がある場合の全体的なスループットへの影響について考える必要があります。
  • ソース文字列を効率的にキューに入れる方法。メモリ内のキューを扱う場合は必ずしも問題ではありませんが、マシン間を移動する場合は問題になる可能性があります。また、基礎となる不変のバイト配列の上にある読み取り専用ラッパーを調べることもできます。

最後に、並行プログラミングの経験がある場合は、このモデルに既に準拠しているいくつかのフレームワーク(たとえば、Akka for Java / Scala)について話すことができます。


プロセッサのL1キャッシュに関するメモ全体に本当に興味がありました。投票しました。
マークディミロ

最近、Spring 5でprojectReactorを使用しました。これにより、スレッドに依存しないコードを作成できます。
クンダンボラ

16

インタビューの質問は、実際にはトリックの質問であり、解決しようとしている問題について考えさせることを目的としています。質問についての質問をすることはある整数の接近の一部いずれかのそれが現実の世界にインタビューでだかどうか、問題を。技術面接での質問への取り組み方について、インターネット上で多数のビデオが公開されています(特にGoogleおよびおそらくMicrosoftをご覧ください)。

「答えようとするだけで、そこから地獄を抜け出します。」

この思考パターンでインタビューに近づくと、働きがいのある会社のインタビューを爆撃することになります。

スレッドから何か得られると思わない場合は、それを伝えてください。なぜメリットがないと思うのを伝えてください。彼らと議論してください。技術面接は、オープンなディスカッションプラットフォームとなることを目的としています。あなたはそれどのように役立つかについて何かを学ぶことになります。面接官があなたに言ったことを盲目的に実行しようとするだけではありません。


3
私はこの質問に答えました(4つのアップ投票を得たにもかかわらず)。
ロバートハーベイ

1
@RobertHarvey:時々間違った質問をする人がいます。OPは技術面接に取り組むための考え方が貧弱であり、この答えは彼/彼女を正しい軌道に乗せるのを助ける試みでした。
デミアンブレヒト

1
@RobertHarvey私は、これが質問に対する正しい答えだと正直に信じています。ここでのキーワードは「インタビューの質問」で、質問のタイトルと本文で言及されています。そのような質問については、これが正しい答えです。「m個のスレッドとn個の単語からなる段落があり、それらを使ってこれをやりたい場合、より良いアプローチは何ですか」という質問だけだった場合、はい、この答えは質問にふさわしくないでしょう。それは素晴らしいことだと思います。言い換え:私はここのアドバイスに従っていなかったので、私はかなりの数の面接の質問を爆撃しました
シヴ山のドラゴン

@RobertHarveyは関連する質問に答えますが、ダウン投票は何も達成しませんでした。
マークディミロ

0
  • 最初に適切な区切り文字で段落をトークン化し、単語をキューに追加します。

  • N個のスレッドを作成し、スレッドプールに保持します。

  • スレッドプールを反復処理してスレッドを開始し、
    スレッドが参加するのを待ちます。そして、最初のスレッドが終了したら次のスレッドを開始します。

  • 各スレッドは、キューをポーリングして印刷するだけです。

  • スレッドプール内ですべてのスレッドが使用されたら、プールの先頭から開始します。


0

あなたが言ったように、私はこのシナリオがスレッド化からまったくメリットがあるとは思わない シングルスレッドの実装よりも遅い可能性があります。

ただし、私の答えは、ワードアレイインデックスへのアクセスを制御するロックにアクセスしようとするタイトループ内の各スレッドを持つことです。各スレッドはロックを取得し、インデックスを取得し、配列から対応する単語を取得して印刷し、インデックスをインクリメントしてからロックを解除します。インデックスが配列の最後にくると、スレッドは終了します。

このようなもの:

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

私はこれが別の要件の後に1つのスレッドを達成するはずだと思いますが、スレッドの順序は保証されていません。他の解決策も聞いてみたいです。


-1

この問題を解決するには、条件待機/シグナルAPIを使用します。

最初のスレッドが1ワードを選択し、その間すべてのスレッドがシグナルを待っているとしましょう。1番目のスレッドは1番目のワードを出力して次のスレッドへのシグナルを生成し、2番目のスレッドは2番目のワードを出力して3番目のスレッドへのシグナルを生成します。

#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ミューテックスを使用してこの問題を解決することも可能です。

使用する場所:

2つのスレッドT1とT2がクリティカルセクションを実行しようとしているとします。どちらも、この重要なセクションの外で行うことはあまりなく、十分な時間ロックを保持します。そのため、T1はT2をロック、実行、およびロック解除し、ウェイクアップのために信号を送ることができます。ただし、T2がウェイクアップしてロックを取得する前に、T1はロックを再取得して実行します。このように、T2は実際にロックを取得するまで待機するか、そうでない場合があります。

仕組み/実装方法:

ロックするミューテックスがあります。各スレッドのスレッド固有データ(TSD)を、スレッドIDとセマフォを含むノードに初期化します。また、所有(TRUEまたはFALSEまたは-1)、所有者(所有者スレッドID)の2つの変数があります。さらに、ウェイターキューと、ウェイターキューの最後のノードを指すポインター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.