कैसे कई धागे के पूरा होने की प्रतीक्षा करें?


109

सभी थ्रेडेड प्रक्रिया को समाप्त करने के लिए बस इंतजार करने का एक तरीका क्या है? उदाहरण के लिए, मान लें कि मेरे पास:

public class DoSomethingInAThread implements Runnable{

    public static void main(String[] args) {
        for (int n=0; n<1000; n++) {
            Thread t = new Thread(new DoSomethingInAThread());
            t.start();
        }
        // wait for all threads' run() methods to complete before continuing
    }

    public void run() {
        // do something here
    }


}

main()जब तक सभी थ्रेड्स के run()तरीके से बाहर नहीं निकल जाते, तब तक मैं इसे कैसे बदलूँ ? धन्यवाद!

जवाबों:


163

आप एक धागे में सभी धागे डालते हैं, उन सभी को शुरू करते हैं, और फिर एक लूप रखते हैं

for(i = 0; i < threads.length; i++)
  threads[i].join();

प्रत्येक जुड़ाव तब तक अवरुद्ध होगा जब तक संबंधित धागा पूरा नहीं हो जाता। आपके द्वारा उनसे जुड़ने की तुलना में थ्रेड एक अलग क्रम में पूर्ण हो सकते हैं, लेकिन यह कोई समस्या नहीं है: जब लूप बाहर निकलता है, तो सभी धागे पूर्ण हो जाते हैं।


1
@ मायकोला: वास्तव में एक थ्रेड समूह का उपयोग करने का क्या फायदा है? सिर्फ इसलिए कि एपीआई वहाँ है इसका मतलब यह नहीं है कि आपको इसका उपयोग करना है ...
मार्टिन v। Löwis

2
देखें: "थ्रेड समूह थ्रेड्स के एक सेट का प्रतिनिधित्व करता है।" इस उपयोग-मामले के लिए यह शब्दार्थ सही है! और: "एक थ्रेड को अपने स्वयं के थ्रेड समूह के बारे में जानकारी तक पहुंचने की अनुमति है"
मार्टिन के।

4
"प्रभावी जावा" पुस्तक थ्रेड समूहों (आइटम 73) से बचने की सिफारिश करती है।
४on में बास्टियन लेओनार्ड

2
प्रभावी जावा में वर्णित कीड़े जावा 6 में तय किए जाने चाहिए थे। यदि नए जावा संस्करण प्रतिबंध नहीं हैं, तो थ्रेड समस्याओं को हल करने के लिए फ्यूचर्स का उपयोग करना बेहतर है। मार्टिन वी। लोविस: आप सही कह रहे हैं। यह उस समस्या के लिए प्रभावी नहीं है, लेकिन एक वस्तु (जैसे ExecutorService) से चल रहे थ्रेड्स के बारे में अधिक जानकारी प्राप्त करना अच्छा है। मुझे लगता है कि किसी समस्या को हल करने के लिए दी गई सुविधाओं का उपयोग करना अच्छा है; शायद आपको भविष्य में और अधिक लचीलापन (थ्रेड जानकारी) की आवश्यकता होगी। पुराने JDKs में पुराने छोटी गाड़ी वर्गों का उल्लेख करना भी सही है।
मार्टिन के।

5
थ्रेडग्रुप एक समूह-स्तरीय जुड़ाव को लागू नहीं करता है, इसलिए लोग थ्रेडग्रुप को क्यों धक्का दे रहे हैं, यह थोड़ा चौंकाने वाला है। क्या लोग वास्तव में स्पिन लॉक का उपयोग कर रहे हैं और समूह के सक्रियकाउंट को क्वेरी कर रहे हैं? आपने मुझे यह समझाने के लिए कड़ी मेहनत की कि किसी भी तरह से यह बेहतर हो जब केवल सभी थ्रेड्स में शामिल होने के लिए कॉलिंग की तुलना में।

41

एक तरह से एक बनाने के लिए किया जाएगा Listकी Threadहै, बना सकते हैं और प्रत्येक थ्रेड लॉन्च करते हैं, जबकि यह सूची में जोड़ने से। एक बार जब सब कुछ लॉन्च हो जाता है, तो सूची के माध्यम से वापस लूप करें और join()प्रत्येक पर कॉल करें । इससे कोई फर्क नहीं पड़ता कि थ्रेड्स किस क्रम में निष्पादित हो रहे हैं, आप सभी को यह जानना होगा कि जब तक दूसरा लूप निष्पादित होता है, तब तक हर धागा पूरा हो जाएगा।

एक एक्सेकॉर्स सर्विस और उससे जुड़े तरीकों का उपयोग करने के लिए एक बेहतर तरीका है :

List<Callable> callables = ... // assemble list of Callables here
                               // Like Runnable but can return a value
ExecutorService execSvc = Executors.newCachedThreadPool();
List<Future<?>> results = execSvc.invokeAll(callables);
// Note: You may not care about the return values, in which case don't
//       bother saving them

ExecutorService (और जावा 5 की संगामिति उपयोगिताओं से सभी नए सामान का उपयोग करना ) अविश्वसनीय रूप से लचीला है, और उपरोक्त उदाहरण भी मुश्किल से सतह को खरोंच कर देता है।


थ्रेडग्रुप जाने का रास्ता है! एक परिवर्तनशील सूची के साथ आप मुसीबत (सिंक्रनाइज़ेशन) में प्राप्त करेंगे
मार्टिन के।

3
क्या? आप परेशानी में कैसे पड़ेंगे? यह थ्रेडिंग के द्वारा केवल उत्परिवर्तनीय (केवल पठनीय) है, इसलिए जब तक यह इसके माध्यम से पुनरावृत्ति करते हुए सूची को संशोधित नहीं करता है, यह ठीक है।
एडम बाटकिन

यह इस बात पर निर्भर करता है कि आप इसका उपयोग कैसे करते हैं। यदि आप थ्रेड में कॉलिंग क्लास का उपयोग करेंगे तो आपको समस्याएँ होंगी।
मार्टिन के।

27
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DoSomethingInAThread implements Runnable
{
   public static void main(String[] args) throws ExecutionException, InterruptedException
   {
      //limit the number of actual threads
      int poolSize = 10;
      ExecutorService service = Executors.newFixedThreadPool(poolSize);
      List<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

      for (int n = 0; n < 1000; n++)
      {
         Future f = service.submit(new DoSomethingInAThread());
         futures.add(f);
      }

      // wait for all tasks to complete before continuing
      for (Future<Runnable> f : futures)
      {
         f.get();
      }

      //shut down the executor service so that this thread can exit
      service.shutdownNow();
   }

   public void run()
   {
      // do something here
   }
}

एक आकर्षण की तरह काम किया ... मैं धागे के दो सेट है जो कई कुकीज़ पर मुद्दों के कारण एक साथ नहीं चलना चाहिए। मैंने एक समय में थ्रेड्स के एक सेट को चलाने के लिए आपके उदाहरण का उपयोग किया था .. आपके ज्ञान को साझा करने के लिए धन्यवाद ...
arn-arn

@ डेंटलियन - आपके रननेबल क्लास (रन पद्धति में होने वाली संभावना) में, आप किसी भी अपवाद को पकड़ना चाहते हैं जो उन्हें हुआ और उन्हें स्थानीय रूप से संग्रहीत करना चाहिए (या एक त्रुटि संदेश / स्थिति संग्रहीत)। उदाहरण में, f.get () आपकी वह वस्तु लौटाता है, जिसे आपने एक्जिक्यूटर सर्विस को सबमिट किया था। आपकी वस्तु किसी भी अपवाद / त्रुटि की स्थिति को पुनः प्राप्त करने के लिए एक विधि हो सकती है। आप प्रदान किए गए उदाहरण को कैसे संशोधित करते हैं, इसके आधार पर, आपको अपने अपेक्षित प्रकार f.get () द्वारा चालू की गई वस्तु को डालना होगा।
जे.टी.

12

इसके बजाय join(), जो एक पुराना API है, आप CountDownLatch का उपयोग कर सकते हैं । मैंने आपकी आवश्यकता को पूरा करने के लिए आपके कोड को नीचे संशोधित किया है।

import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable{
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch){
        this.latch = latch;
    } 
    public void run() {
        try{
            System.out.println("Do some thing");
            latch.countDown();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try{
            CountDownLatch latch = new CountDownLatch(1000);
            for (int n=0; n<1000; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of 1000 threads");
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

स्पष्टीकरण :

  1. CountDownLatch आपकी आवश्यकता के अनुसार दिए गए गिनती 1000 के साथ आरंभीकृत किया गया है।

  2. प्रत्येक वर्कर थ्रेड DoSomethingInAThread डिक्रिप्ट करेगा CountDownLatch, जिसे कंस्ट्रक्टर में पास किया गया है।

  3. CountDownLatchDemo await()गिनती तक मुख्य धागा शून्य हो गया है। एक बार गिनती शून्य हो जाने के बाद, आपको आउटपुट में नीचे की रेखा मिलेगी।

    In Main thread after completion of 1000 threads

ओरेकल प्रलेखन पृष्ठ से अधिक जानकारी

public void await()
           throws InterruptedException

जब तक कुंडी शून्य तक गिना नहीं जाता है, तब तक प्रतीक्षा करने के लिए वर्तमान धागे का कारण बनता है, जब तक कि धागा बाधित नहीं होता है।

अन्य विकल्पों के लिए संबंधित एसई प्रश्न का संदर्भ लें:

जब तक सभी धागे जावा में अपना काम खत्म नहीं करते तब तक प्रतीक्षा करें


8

थ्रेड क्लास से पूरी तरह बचें और इसके बजाय java.util.concurrent में दिए गए उच्च अमूर्त का उपयोग करें

ExecutorService वर्ग विधि invokeAll प्रदान करता है जो ऐसा लगता है जो आप चाहते हैं।


6

का उपयोग करने पर विचार करें java.util.concurrent.CountDownLatchJavadocs में उदाहरण


थ्रेड्स के लिए एक कुंडी है, कुंडी ताला एक उलटी गिनती के साथ काम करता है। अपने थ्रेड के रन () विधि में स्पष्ट रूप से एक काउंटडाउन लॉच का इंतजार करने का ऐलान करें। यह काउंटडाउन 0. तक पहुंचने के लिए है। आप एक साथ जारी करने के लिए एक से अधिक थ्रेड में एक ही काउंटडाउन लच का उपयोग कर सकते हैं। मुझे नहीं पता कि यह वही है जो आपको चाहिए, बस इसका उल्लेख करना चाहता था क्योंकि यह एक बहुपरत वातावरण में काम करते समय उपयोगी है।
पाब्लो कैवलियरी

हो सकता है कि आप अपने जवाब के शरीर में उस स्पष्टीकरण को डाल दें?
हारून हॉल

जावदोक में उदाहरण बहुत वर्णनात्मक हैं, यही कारण है कि मैंने कोई जोड़ नहीं दिया। docs.oracle.com/javase/7/docs/api/java/util/concurrent/… । पहले उदाहरण में, सभी वर्कर्स थ्रेड्स एक साथ रिलीज़ होते हैं क्योंकि वे काउंटडाउनचैच स्टार्टसिग्नल के शून्य पर पहुंचने का इंतजार करते हैं, जो कि स्टार्टसिग्नल.डाउनडाउन () में होता है। फिर, मियां धागा तब तक प्रतीक्षा करता है जब तक कि सभी निर्देश दिए गए निर्देश का उपयोग करके समाप्त नहीं हो जाते हैं। Signal.await ()। सग्नल प्रत्येक कार्यकर्ता में इसके मूल्य को कम करता है।
पाब्लो कैवलियरी

6

जैसा कि मार्टिन के ने सुझाव दिया था java.util.concurrent.CountDownLatchकि यह एक बेहतर समाधान है। बस उसी के लिए एक उदाहरण जोड़ रहा हूं

     public class CountDownLatchDemo
{

    public static void main (String[] args)
    {
        int noOfThreads = 5;
        // Declare the count down latch based on the number of threads you need
        // to wait on
        final CountDownLatch executionCompleted = new CountDownLatch(noOfThreads);
        for (int i = 0; i < noOfThreads; i++)
        {
            new Thread()
            {

                @Override
                public void run ()
                {

                    System.out.println("I am executed by :" + Thread.currentThread().getName());
                    try
                    {
                        // Dummy sleep
                        Thread.sleep(3000);
                        // One thread has completed its job
                        executionCompleted.countDown();
                    }
                    catch (InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }.start();
        }

        try
        {
            // Wait till the count down latch opens.In the given case till five
            // times countDown method is invoked
            executionCompleted.await();
            System.out.println("All over");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

4

अपनी आवश्यकताओं के आधार पर, आप java.util.concurrent पैकेज में CountDownLatch और CyclicBarrier की जांच कर सकते हैं। वे उपयोगी हो सकते हैं यदि आप चाहते हैं कि आपके धागे एक-दूसरे के लिए प्रतीक्षा करें, या यदि आप अपने धागे को निष्पादित करने के तरीके पर अधिक ठीक-ठीक नियंत्रण चाहते हैं (जैसे, किसी राज्य को सेट करने के लिए दूसरे धागे के लिए उनके आंतरिक निष्पादन में इंतजार करना)। आप अपने सभी थ्रेड्स को एक ही समय में शुरू करने के बजाय, अपने लूप के माध्यम से पुनरावृति करने के बजाय एक-एक करके शुरू करने का संकेत देने के लिए एक काउंटडाउनचैट का उपयोग कर सकते हैं। मानक एपीआई डॉक्स का एक उदाहरण है, इसके अलावा एक और काउंटडाउनचैच का उपयोग करके सभी थ्रेड के लिए अपने निष्पादन को पूरा करने के लिए प्रतीक्षा करें।


3

यदि आप थ्रेड्स की एक सूची बनाते हैं, तो आप उनके माध्यम से लूप कर सकते हैं और प्रत्येक के खिलाफ .join () कर सकते हैं, और जब सभी थ्रेड्स होंगे तो आपका लूप समाप्त हो जाएगा। मैंने हालांकि यह कोशिश नहीं की है।

http://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#join ()


नमस्ते, यह मेरे लिए किसी कारण से काम नहीं आया। यहाँ मेरा सवाल है: stackoverflow.com/users/5144855/ruchir-baronia
रुचिर बरोनिया

1

लूप के लिए पहले के अंदर थ्रेड ऑब्जेक्ट बनाएं।

for (int i = 0; i < threads.length; i++) {
     threads[i] = new Thread(new Runnable() {
         public void run() {
             // some code to run in parallel
         }
     });
     threads[i].start();
 }

और फिर यहाँ हर कोई क्या कह रहा है।

for(i = 0; i < threads.length; i++)
  threads[i].join();

0

आप इसे ऑब्जेक्ट "थ्रेडग्रुप" और इसके पैरामीटर सक्रियकाउंट के साथ कर सकते हैं :


यकीन नहीं है कि आप इसे करने का प्रस्ताव कैसे देते हैं। यदि आप एक सक्रिय लूप में मतदान करने का प्रस्ताव देते हैं: यह बुरा है, क्योंकि यह व्यस्त-प्रतीक्षा है (भले ही आप चुनावों के बीच सोते हों - फिर आपको व्यवसाय और जवाबदेही के बीच एक व्यापार मिल जाता है)।
मार्टिन वी। लोविस

@ मर्टिन वी। लोविस: "जॉइन सिर्फ एक धागे की प्रतीक्षा करेगा। एक बेहतर समाधान एक java.util.concurrent.CountDownLatch हो सकता है। बस कार्यकर्ता थ्रेड्स की संख्या के लिए सेट सेट के साथ कुंडी को इनिशियलाइज़ करें। प्रत्येक वर्कर थ्रेड को कॉल करना चाहिए। काउंटडाउन () से बाहर निकलने से ठीक पहले, और मुख्य थ्रेड बस वेट () कहता है, जो काउंटर के शून्य तक पहुंचने तक अवरुद्ध हो जाएगा। शामिल होने के साथ समस्या () यह भी है कि आप गतिशील रूप से अधिक थ्रेड जोड़ना शुरू नहीं कर सकते हैं। सूची फट जाएगी। एक समवर्ती संशोधन के साथ। " आपका समाधान समस्या के लिए ठीक काम करता है लेकिन सामान्य उद्देश्य के लिए नहीं।
मार्टिन के।

0

करने के लिए एक विकल्प के रूप CountDownLatch आप भी उपयोग कर सकते हैं CyclicBarrier जैसे

public class ThreadWaitEx {
    static CyclicBarrier barrier = new CyclicBarrier(100, new Runnable(){
        public void run(){
            System.out.println("clean up job after all tasks are done.");
        }
    });
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new MyCallable(barrier));
            t.start();
        }       
    }

}    

class MyCallable implements Runnable{
    private CyclicBarrier b = null;
    public MyCallable(CyclicBarrier b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
            //do something
            System.out.println(Thread.currentThread().getName()+" is waiting for barrier after completing his job.");
            b.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }       
}

इस मामले में CyclicBarrier का उपयोग करने के लिए बाधा। () अंतिम विवरण होना चाहिए जब आपका धागा अपनी नौकरी के साथ किया जाता है। CyclicBarrier को इसके रीसेट () विधि के साथ फिर से उपयोग किया जा सकता है। Javadocs बोली करने के लिए:

एक CyclicBarrier एक वैकल्पिक Runnable कमांड का समर्थन करता है, जो एक बार प्रति बाधा बिंदु पर चलाया जाता है, पार्टी में आखिरी धागा आने के बाद, लेकिन किसी भी थ्रेड को जारी करने से पहले। यह बाधा कार्रवाई किसी भी पक्ष के जारी रहने से पहले साझा-स्थिति को अपडेट करने के लिए उपयोगी है।


मुझे नहीं लगता कि यह साइक्लिक बैरियर के लिए एक अच्छा उदाहरण है। आप थ्रेड.स्लीप () कॉल का उपयोग क्यों करते हैं?
गुटेनर

@Guenther - हाँ, मैंने आवश्यकता के अनुरूप कोड को बदल दिया है।
शैलेंद्र 1118

CyclicBarrier CountDownLatch का विकल्प नहीं है। जब थ्रेड्स को बार-बार गिना जाना चाहिए, तो आपको CyclicBarrier बनाना चाहिए, अन्यथा CountDownLatch के लिए डिफ़ॉल्ट (जब तक कि अतिरिक्त निष्पादन की अतिरिक्त अमूर्त की आवश्यकता न हो, जिस बिंदु पर आपको उच्च-स्तरीय, सेवाओं को देखना चाहिए)।
एलीसिमप्लैन

0

join()मेरे लिए उपयोगी नहीं था। कोटलिन में यह नमूना देखें:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
        }
    })

परिणाम:

Thread-5|a=5
Thread-1|a=1
Thread-3|a=3
Thread-2|a=2
Thread-4|a=4
Thread-2|2*1=2
Thread-3|3*1=3
Thread-1|1*1=1
Thread-5|5*1=5
Thread-4|4*1=4
Thread-1|2*2=2
Thread-5|10*2=10
Thread-3|6*2=6
Thread-4|8*2=8
Thread-2|4*2=4
Thread-3|18*3=18
Thread-1|6*3=6
Thread-5|30*3=30
Thread-2|12*3=12
Thread-4|24*3=24
Thread-4|96*4=96
Thread-2|48*4=48
Thread-5|120*4=120
Thread-1|24*4=24
Thread-3|72*4=72
Thread-5|600*5=600
Thread-4|480*5=480
Thread-3|360*5=360
Thread-1|120*5=120
Thread-2|240*5=240
Thread-1|TaskDurationInMillis = 765
Thread-3|TaskDurationInMillis = 765
Thread-4|TaskDurationInMillis = 765
Thread-5|TaskDurationInMillis = 765
Thread-2|TaskDurationInMillis = 765

अब मुझे join()थ्रेड्स के लिए उपयोग करें :

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
            t.join()
        }
    })

और परिणाम:

Thread-1|a=1
Thread-1|1*1=1
Thread-1|2*2=2
Thread-1|6*3=6
Thread-1|24*4=24
Thread-1|120*5=120
Thread-1|TaskDurationInMillis = 815
Thread-2|a=2
Thread-2|2*1=2
Thread-2|4*2=4
Thread-2|12*3=12
Thread-2|48*4=48
Thread-2|240*5=240
Thread-2|TaskDurationInMillis = 1568
Thread-3|a=3
Thread-3|3*1=3
Thread-3|6*2=6
Thread-3|18*3=18
Thread-3|72*4=72
Thread-3|360*5=360
Thread-3|TaskDurationInMillis = 2323
Thread-4|a=4
Thread-4|4*1=4
Thread-4|8*2=8
Thread-4|24*3=24
Thread-4|96*4=96
Thread-4|480*5=480
Thread-4|TaskDurationInMillis = 3078
Thread-5|a=5
Thread-5|5*1=5
Thread-5|10*2=10
Thread-5|30*3=30
Thread-5|120*4=120
Thread-5|600*5=600
Thread-5|TaskDurationInMillis = 3833

जब हम इसका उपयोग करते हैं तो यह स्पष्ट है join:

  1. सूत्र क्रम से चल रहे हैं।
  2. पहला नमूना 765 मिलिसेकंड लेता है जबकि दूसरा नमूना 3833 मिलिसेकंड लेता है।

अन्य थ्रेड्स को रोकने के लिए हमारा समाधान एक ArrayList बना रहा था:

val threads = ArrayList<Thread>()

अब जब हम एक नया सूत्र शुरू करना चाहते हैं तो हम इसे ArrayList में जोड़ देते हैं:

addThreadToArray(
    ThreadUtils.startNewThread(Runnable {
        ...
    })
)

addThreadToArrayसमारोह:

@Synchronized
fun addThreadToArray(th: Thread) {
    threads.add(th)
}

startNewThreadfunstion:

fun startNewThread(runnable: Runnable) : Thread {
    val th = Thread(runnable)
    th.isDaemon = false
    th.priority = Thread.MAX_PRIORITY
    th.start()
    return th
}

हर जगह नीचे दिए गए धागे के पूरा होने की जाँच करें:

val notAliveThreads = ArrayList<Thread>()
for (t in threads)
    if (!t.isAlive)
        notAliveThreads.add(t)
threads.removeAll(notAliveThreads)
if (threads.size == 0){
    // The size is 0 -> there is no alive threads.
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.