Çok okuyuculu senkronizasyon görüşme sorusu: m thread verilen n kelimesini bulun


23

Bu sorunun, tek bir iş parçacığı yerine birden çok iş parçacığı olan bir çözümden yararlanabileceği bir yol var mı?


Bir röportajda birden fazla iş parçacığı kullanarak bir sorunu çözmem istendi. Bana öyle geliyor ki çoklu iplikler fayda sağlamıyor.

İşte sorun:

Size n kelime içeren bir paragraf verilir, size m thread verilir. Yapmanız gereken, her bir konu bir kelime basmalı ve kontrolü bir sonraki konuya vermelidir, bu şekilde her bir konu bir kelime basmaya devam edecektir, en son konu geldiğinde ilk konu başlatılmalıdır. Tüm kelimeler paragrafta basılana kadar yazdırma işlemi tekrarlanacaktır. Sonunda, tüm dişler dikkatlice çıkmalı. Ne tür bir senkronizasyon kullanacak?

Buradaki konuların hiçbir avantajından yararlanamayacağımızı kuvvetle hissediyorum, ancak görüşmecinin senkronizasyon becerilerimi ölçmeye çalıştığına inanıyoruz. Bu problemde birden fazla iş parçacığının değeri olan bir şey eksik mi?

Kod gerekmez, sadece bazı düşünceler koyun. Kendi başıma uygulayacağım.


C ++ etiketi eklemek muhtemelen burada pek yardımcı olmaz. Buradaki bu sorular, herhangi bir dili aşan daha kavramsal şeylerdir.
cHao

Hislerine güven. Neye gittiklerini anlıyorum, ancak problemi gerçek dünyada nasıl çözmeniz gerektiğinden sapan soruları hiç beğenmedim .
G_P

16
@ rplusg - Çözümün sorunu seri hale getirdiğine ve herhangi bir eşzamanlı işlem yapmadan sadece iş parçacığı eklediğine işaret eden bir görüşmenden çok daha fazla etkilenirdim. Görüşme yapan kişi, soruyu istediğiniz gibi cevaplamanız konusunda her zaman ısrar edebilir.
David Harkness

eğer "her konu bir kelime basmalı ve kontrolü bir sonraki konuya vermeli", seri iş gibi, yani bir konu önceki bir parçanın bitmesini bekliyor ve bir röleyi geçmek gibi. neden bu durumda sadece tek parçalı bir uygulama yapmıyorsunuz?
amfi

1
Anladım @Blrfl. Bu, X aracını nasıl kullanacağınızı bildiğinizi doğrulamam gibi bir şey ama o eldeki ilk aracı ne kullandıysam ve elime aldığımda, sadece bu aracı kullanmayı garanti eden gerçek bir uygulama kullanım senaryosu tasarlamak için çok tembel ya da özensizdi. içine eğimli olarak. Açıkçası, eğer bir röportajda söyleseydim, onu çağırırdım ve muhtemelen özensiz ve yarı yarıya bir insanla çalışmak istemem
Ocak'taki

Yanıtlar:


22

Sizi bir semafor çözümüne yönlendirdikleri gibi geliyor. Semaforlar, sıralarının bir başka ipucuna işaret etmek için kullanılır. Mutekslerden çok daha az kullanılırlar, bu yüzden bence bunun iyi bir röportaj sorusu olduğunu düşünüyorlar. Aynı zamanda örneğin kabul görmüş görünmesinin nedeni de budur.

Temel olarak, msemaforlar yaratırsınız. Her iş parçacığı xsemafor üzerinde bekler, xsonra bir x+1şey yaptıktan sonra semafor'a gönderir . Sözde kodda:

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])

Ödül için teşekkürler. Üzerine farenin, kimin verdiğini söylemesi biraz zaman aldı.
kdgregory

Affedersiniz, cehalet, bu çözümün nasıl doğru olduğu hakkında daha fazla bilgi verebilir misiniz? bu yeni bir fantezi semafor mu? Ancak sorunun [semaforların kullandığı] bir bekleme / bildirim çözümü ile çözüldüğünden eminim.
AJed

Bu sadece bir standart semafor dizisi. Onlar hakkında özel bir şey yok. Bazı uygulamalarda bildirim "posta" olarak adlandırılır.
Karl Bielefeldt

@KarlBielefeldt Her x iş parçacığı semafor x için bekleyecekse, o zaman tüm iş parçacıkları engellenecek ve hiçbir şey olmayacak. Eğer bekle (sem) aslında kazanıyorsa (sem) - o zaman aynı anda girerler ve dışlanma olmaz. Daha fazla açıklama yapılıncaya kadar, bu sözde kodda yanlış bir şey olduğuna inanıyorum ve en iyi cevap olmamalıdır.
AJed

Bu sadece her iş parçacığı için döngü gösteriyor. Kurulum kodu işleri başlatmak için ilk semaforda yayın yapmak zorunda kalacaktı.
Karl Bielefeldt

23

Kanımca, bu müthiş bir mülakat sorusu - en azından (1) adayın iş parçacığı bilgisine sahip olması beklenir ve (2) görüşmeci aynı zamanda derin bilgiye sahip ve soruyu araştırmak için soruyu kullanıyor. Görüşme yapan kişinin belirli ve dar bir cevap araması her zaman mümkündür, ancak yetkili bir görüşmeci aşağıdakileri aramalıdır:

  • Soyut kavramları somut uygulamalardan ayırt edebilme. Bunu öncelikle yorumların bazılarına meta-yorum olarak atıyorum. Hayır, tek bir kelime listesini bu şekilde işlemenin bir anlamı yok. Bununla birlikte, farklı yeteneklere sahip birden fazla makineye yayılabilen bir işlem hattı soyut kavramı önemlidir.
  • Tecrübelerime göre (yaklaşık 30 yıllık dağıtılmış, çok işlemli ve çok iş parçacıklı uygulamalar), çalışmayı dağıtmak zor değil. Sonuçları toplamak ve bağımsız süreçleri koordine etmek , en fazla iş parçacığı hatalarının meydana geldiği yerlerdir (yine benim tecrübeme göre). Sorunu basit bir zincire indirgeyerek, görüşmeci adayın koordinasyon hakkında ne kadar iyi düşündüğünü görebilir. Ayrıca görüşmeci, "Tamam, ya da her bir başlığın yeniden yapılanma için başka bir başlığa söz vermesi gerekiyorsa" gibi her türlü takip eden soruyu sorma fırsatına sahiptir.
  • Aday, işlemcinin bellek modelinin uygulamayı nasıl etkileyebileceğini düşünüyor mu? Bir işlemin sonuçları hiçbir zaman L1 önbellekten silinmezse, görünür eşzamanlılık olmasa bile bu bir hatadır.
  • Aday, iş parçacığını uygulama mantığından ayırıyor mu?

Bu son nokta, bence en önemlisi. Yine, deneyimlerime dayanarak, iş parçacığı uygulama mantığı ile karıştırılırsa, iş parçacığı kodunda hata ayıklamak zorlaşır (örnekler için SO'daki tüm Swing sorularına bakın). En iyi çok iş parçacıklı kodun açıkça tanımlanmış aktarmalarla kendi kendine yeten tek iş parçacıklı kod olarak yazıldığına inanıyorum.

Bunu akılda tutarak, yaklaşımım her iş parçacığına iki sıra vermek olacaktır: biri girdi, biri çıktı. İş parçacığı giriş kuyruğunu okurken engeller, ilk sözcüğü dizeden alır ve dizenin kalanını çıkış kuyruğuna geçirir. Bu yaklaşımın özelliklerinden bazıları:

  • Başvuru kodu bir sıra okumak, verilere bir şey yapmak ve sıra yazmaktan sorumludur. Çok iş parçacıklı olup olmamasına ya da sıranın bir makinede bellekteki bir sıraya mı yoksa dünyanın karşı taraflarında yaşayan makineler arasında TCP tabanlı bir sıraya mı bakacağı önemli değil.
  • Uygulama kodu tek iş parçacıklı gibi yazıldığından, çok sayıda iskele gerektirmeden belirleyici bir şekilde test edilebilir.
  • Uygulama aşaması boyunca, uygulama kodu işlenen dizeye sahiptir. Eşzamanlı çalışan iş parçacığı ile senkronizasyon ile ilgilenmek zorunda değildir.

Bununla birlikte, yetkin bir görüşmecinin sorgulayabileceği pek çok gri alan var:

  • “Tamam, ancak eşzamanlılık ilkelleri hakkındaki bilgilerinizi görmek istiyoruz; engelleme kuyruğunu uygulayabilir misiniz?” İlk cevabınız, elbette, seçtiğiniz platformdan önceden oluşturulmuş bir engelleme kuyruğu kullanmanız gerektiğidir. Ancak, iş parçacığını anlarsanız, platformunuzun desteklediği herhangi bir senkronizasyon ilkesini kullanarak bir düzine kod satırında bir kuyruk uygulaması oluşturabilirsiniz.
  • “Süreçteki bir adım çok uzun sürerse ne olur?” Sınırlandırılmış veya sınırlandırılmamış bir çıkış kuyruğu isteyip istemediğinizi, hataları nasıl idare edebileceğinizi ve bir gecikme durumunda genel işlem üzerindeki etkilerini isteyip istemediğinizi düşünmelisiniz.
  • Kaynak dizgiyi verimli bir şekilde nasıl sıkıştırabilirim? Bellek içi sıralarla ilgileniyorsanız mutlaka sorun değil, makineler arasında hareket ediyorsanız sorun olabilir. Ayrıca, değişmez bir bayt dizisinin en üstünde salt okunur sarmalayıcıları da keşfedebilirsiniz.

Son olarak, eşzamanlı programlama konusunda deneyiminiz varsa, zaten bu modeli izleyen çerçevelerden (örneğin, Akka for Java / Scala) bahsedebilirsiniz.


İşlemcinin L1 önbelleği hakkındaki o not beni çok etkiledi. Oy verildi.
Marc DiMillo

Geçenlerde projectReactor'ı Spring 5 ile kullandım. Bu, konu ile ilgili agnostik kod yazmamı sağlıyor.
kundan bora

16

Mülakat soruları, bazen çözmeye çalıştığınız sorun hakkında düşünmenizi sağlamak için hileli sorulardır . Bir soru hakkında soru sormak , ister gerçek dünyada ister röportajda olsun, herhangi bir soruna yaklaşmanın ayrılmaz bir parçasıdır . Teknik görüşmelerde sorulara nasıl yaklaşılacağı konusunda internette dolaşan bir dizi video var (özellikle Google ve belki de Microsoft’u arayın).

“Sadece cevap vermeye çalış ve cehennemden çık ..”

Bu düşünce modeliyle yapılan görüşmelere yaklaşmak, üzerinde çalışmaya değer olan herhangi bir şirket için yapılan görüşmeleri bombalamanıza yol açacaktır.

Çok fazla kazanacağınızı düşünmüyorsanız (iş parçacığından bir şey varsa), onlara bunu söyleyin. Onlara neden faydası olmadığını düşündüğünü söyle . Onlarla bir tartışma yapın. Teknik görüşmeler açık bir tartışma platformu olmak içindir. Nasıl yararlı olabileceği hakkında bir şeyler öğrenmeye başlayabilirsiniz . Görme engelli görüşmeninizin size söylediği bir şeyi uygulamaya çalışmaya çalışmayın.


3
Bu cevabı reddettim (açıklanamayan 4 oy almasına rağmen), çünkü sorulan soruyu cevaplamıyor.
Robert Harvey,

1
@RobertHarvey: Bazen insanlar yanlış sorular sorarlar . OP'nin teknik görüşmelerle başa çıkmak için zayıf bir zihniyeti var ve bu cevap onu doğru yola koyma çabasıydı.
Demian Brecht

1
@RobertHarvey Dürüst olmak gerekirse, bunun soru için doğru cevap olduğuna inanıyorum. Buradaki anahtar kelime, başlığın içinde ve sorunun gövdesinde belirtilen “mülakat sorusu” dur. Böyle bir soru için doğru cevap budur. Eğer soru sadece "m konularım ve bir n kelimeden oluşan bir paragrafım var ve bunu yapmak istiyorum ve onlarla birlikte, daha iyi bir yaklaşım nedir?", O zaman evet, bu cevap soru için uygun olmazdı. Sanırım harika. Başka bir deyişle: Bir kaç görüşme sorusunu bombaladım, çünkü burada verilen tavsiyelere
uymadım

@RobertHarvey ilgili bir soruya cevap verdi, aşağı oylama hiçbir şey yapmadı.
Marc DiMillo

0
  • Önce paragrafı uygun sınırlayıcılarla belirtin ve sözcükleri bir Sıraya ekleyin.

  • N sayısı iş parçacığı oluşturun ve bir iş parçacığı havuzunda tutun.

  • İş parçacığı havuzu üzerinde yineleyin ve iş parçacığını başlatın ve iş
    parçacığının birleşmesini bekleyin . Ve ilk iş parçacığı bittikten sonra bir sonraki iş parçacığına başlayın.

  • Her iş parçacığı sadece kuyruğu yoklamalı ve yazdırmalıdır.

  • İş parçacığı havuzu içinde tüm iş parçacıkları kullanıldığında, havuzun başından başlayın.


0

Söylediğiniz gibi, bu senaryonun hiç bir şekilde diş açmasından çok fayda sağlayacağını sanmıyorum. Tek bir dişli uygulamadan daha yavaştır.

Ancak, cevabım her iş parçacığının bir dizi döngü dizisi kelime erişimini kontrol eden bir kilit erişmeye çalışırken bir döngü içinde sahip olmak olacaktır. Her iş parçacığı kilidi kapar, dizini alır, diziden karşılık gelen sözcüğü alır, yazdırır, dizini artırır, sonra kilidi serbest bırakır. Dizin dizinin sonunda olduğunda threadlar çıkar.

Bunun gibi bir şey:

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

Bunun bir ipliği başka bir gereksinimden sonra başarması gerektiğini düşünüyorum, ancak dişlerin sıralanması garanti edilmiyor. Diğer çözümleri de duymak istiyorum.


-1

Bu sorunu çözmek için koşul bekle / sinyal API'lerini kullanın.

Diyelim ki ilk konu 1 kelime seçiyor ve bu arada tüm konuların bir sinyal bekliyor. 1. iplik birinci kelimeyi yazdırır ve bir sonraki konuya sinyal verir ve ardından 2. iplik ikinci kelimeyi yazdırır ve 3. iplik için sinyal üretir.

#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

[Burada kullanılan terimler POSIX konularına özel olabilir]

Bu sorunu çözmek için bir FIFO muteks kullanmak da mümkün olmalıdır.

Nerede kullanmalı:

İki iplik T1 ve T2'nin kritik bir bölüm yürütmeye çalıştığını varsayalım. Her ikisinin de bu kritik bölümün dışında yapacak çok şeyi yok ve kilitleri iyi bir süre tutuyorlar. Böylece, T1, uyanma için T2'yi kilitleyebilir, çalıştırabilir ve kilidini açabilir ve sinyal gönderebilir. Ancak T2 uyanıp kilitlenmeden önce, T1 kilitlenip çalıştırılır. Bu şekilde, T2 gerçekten kilitlenmeden önce çok uzun süre beklemek zorunda kalabilir veya olmayabilir.

Nasıl çalışır / Nasıl uygulanır?

Kilitlemek için bir muteks var. Her iş parçacığı için iş parçacığına özgü verileri (TSD) iş parçacığı tanıtıcısı ve semafor içeren bir düğüme başlat. Ayrıca, iki değişkene sahip - sahip (DOĞRU veya YANLIŞ veya -1), sahip (sahip iş parçacığı kimliği). Ayrıca, garsonların kuyruğunu ve garsonların kuyruğundaki son düğüme işaret eden bir garsonu göster.

kilit işlemi:

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

operasyon kilidini açma:

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

-1

İlginç soru. İşte iş parçacığı arasında buluşma kanalı oluşturmak için SynchronousQueue kullanarak Java'daki çözümüm:

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

Bu tür bir soruyu cevaplamanın çok zor olduğunu söyleyebilirim, çünkü tamamen aptalca bir şey yapmanın en iyi yolunu sorar. Beynim bu şekilde çalışmıyor. Aptal sorular için çözümler bulamıyor. Beynim hemen bu koşullar altında, birden fazla iplik kullanmanın anlamsız olduğunu söyleyecektir, bu yüzden tek bir iplik kullanacağım.

Daha sonra, onlardan diş açma hakkında gerçek bir dünya sorusu sormalarını ya da gerçek dünyaya ciddi bir iş parçacığı örneği vermemi isterdim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.