जावा मल्टीथ्रेडिंग में काउंटडाउन लॉच का उपयोग कैसे किया जाता है?


183

क्या कोई मुझे समझने में मदद कर सकता है कि जावा क्या CountDownLatchहै और इसका उपयोग कब करना है?

मेरे पास इस कार्यक्रम के काम करने का बहुत स्पष्ट विचार नहीं है। जैसा कि मैं समझता हूं कि सभी तीन धागे एक साथ शुरू होते हैं और प्रत्येक थ्रेड 3000ms के बाद काउंटडाउनचैट को कॉल करेगा। तो गिनती एक-एक करके घटती जाएगी। कुंडी शून्य हो जाने के बाद कार्यक्रम "पूर्ण" प्रिंट करता है। हो सकता है कि जिस तरह से मैंने समझा है वह गलत है।

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Processor implements Runnable {
    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {
        System.out.println("Started.");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        latch.countDown();
    }
}

// ------------------------------------------------ -----

public class App {

    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0

        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool

        for(int i=0; i < 3; i++) {
            executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
        }

        try {
            latch.await();  // wait until latch counted down to 0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Completed.");
    }

}


8
मैंने एंड्रॉइड समानांतर सर्विस बैच के लिए आपके प्रश्न के नमूने कोड का उपयोग किया है और यह एक आकर्षण की तरह काम करता है। आपको बहुत - बहुत धन्यवाद!
Roisgoen

2012 के इस वीडियो से यहां मिला , जो यहां दिखाए गए उदाहरण से उल्लेखनीय समानता दिखाता है। रुचि रखने वाले किसी भी व्यक्ति के लिए, यह जॉन नाम के लड़के से जावा मल्टी-थ्रेडिंग ट्यूटोरियल श्रृंखला का हिस्सा है। मुझे जॉन पसंद है। अत्यधिक सिफारिशित।
एलिया ग्रैडी

जवाबों:


194

जी हां, आपने सही समझा। CountDownLatchकुंडी सिद्धांत में काम करता है, मुख्य धागा तब तक इंतजार करेगा जब तक कि गेट खुला न हो। एक थ्रेड n थ्रेड्स की प्रतीक्षा करता है , जिसे बनाते समय निर्दिष्ट किया जाता है CountDownLatch

कोई भी धागा, आमतौर पर एप्लिकेशन का मुख्य धागा, जो कॉल CountDownLatch.await()शून्य तक पहुंचने तक प्रतीक्षा करेगा या किसी अन्य धागे द्वारा बाधित होता है। CountDownLatch.countDown()एक बार पूरा होने या तैयार होने पर कॉल करके अन्य सभी थ्रेड्स की गणना करने की आवश्यकता होती है ।

जैसे ही गिनती शून्य पर पहुंचती है, प्रतीक्षा धागा जारी रहता है। एक नुकसान / लाभ CountDownLatchयह है कि यह पुन: प्रयोज्य नहीं है: एक बार गिनती शून्य तक पहुंच जाती है आप CountDownLatchकिसी भी अधिक उपयोग नहीं कर सकते ।

संपादित करें:

उपयोग CountDownLatch जब एक धागा (मुख्य धागे की तरह) से पहले ही प्रसंस्करण जारी रख सकते हैं, पूरा करने के लिए एक या अधिक सूत्र के लिए प्रतीक्षा करने की आवश्यकता है।

CountDownLatchजावा में उपयोग करने का एक शास्त्रीय उदाहरण एक सर्वर साइड कोर जावा एप्लिकेशन है जो सेवाओं की वास्तुकला का उपयोग करता है, जहां कई सेवाएं कई थ्रेड्स द्वारा प्रदान की जाती हैं और एप्लिकेशन तब तक प्रसंस्करण शुरू नहीं कर सकता जब तक कि सभी सेवाएं सफलतापूर्वक शुरू नहीं हुई हैं।

पीएस ओपी के सवाल का बहुत सीधा उदाहरण है इसलिए मैंने एक को भी शामिल नहीं किया।


1
जवाब देने के लिए धन्यवाद। क्या आप मुझे एक उदाहरण दे सकते हैं कि काउंटडाउन कुंडी कैसे लगाई जाए?
अमल


1
@NikolaB लेकिन इस दिए गए उदाहरण में हम शामिल होने की विधि का उपयोग करके एक ही परिणाम प्राप्त कर सकते हैं?
विकास वर्मा

3
मैं गैर-पुन: प्रयोज्य एक लाभ पर विचार करूंगा: आपको यकीन है कि कोई भी इसे रीसेट नहीं कर सकता है, या गिनती बढ़ा सकता है।
अताउल्म

3
अच्छी व्याख्या। लेकिन मैं इस मुद्दे पर थोड़ा असहमत होगा One thread waits for n number of threads specified while creating CountDownLatch in Java। यदि आपको ऐसे तंत्र की आवश्यकता है, तो इसका उपयोग करना समझदारी है CyclicBarrier। इन दोनों के बीच मौलिक वैचारिक अंतर है, के रूप में में दी गई Java concurrency in Practiceहै: Latches are for waiting for events; barriers are for waiting for other threadscyclicBarrier.await()एक अवरुद्ध स्थिति में चला जाता है।
राहुल देव मिश्रा

43

CountDownLatchजावा में एक प्रकार का सिंक्रोनाइज़र है जो प्रसंस्करण शुरू करने से पहले Thread एक या अधिक Threadएस के लिए प्रतीक्षा करने की अनुमति देता है ।

CountDownLatchकुंडी सिद्धांत पर काम करता है, धागा तब तक इंतजार करेगा जब तक कि गेट खुला न हो। एक धागा nबनाते समय निर्दिष्ट थ्रेड की संख्या की प्रतीक्षा करता है CountDownLatch

जैसे final CountDownLatch latch = new CountDownLatch(3);

यहां हमने काउंटर को 3 पर सेट किया।

कोई भी धागा, आमतौर पर आवेदन का मुख्य धागा, जो कॉल CountDownLatch.await()शून्य तक पहुंचने तक प्रतीक्षा करेगा या इसे किसी अन्य द्वारा बाधित किया जाता है ThreadCountDownLatch.countDown()एक बार पूरा होने या नौकरी के लिए तैयार होने पर अन्य सभी थ्रेड्स को कॉल करके गिनना आवश्यक है । जैसे ही गिनती शून्य तक पहुँचती है,Thread प्रतीक्षा शुरू होती है।

यहाँ गिनती में कमी आती है CountDownLatch.countDown() विधि है।

Threadजो कहता हैawait() विधि शून्य करने के लिए प्रारंभिक गिनती पहुँच तक इंतजार करेंगे।

गिनती करने के लिए शून्य अन्य थ्रेड को कॉल करने की आवश्यकता होती है countDown() विधि । एक बार गिनती शून्य हो जाती है जो await()विधि लागू होती है फिर से शुरू होगी (इसका निष्पादन शुरू करें)।

इसका नुकसान CountDownLatchयह है कि यह पुन: प्रयोज्य नहीं है: एक बार गिनती शून्य हो जाने के बाद यह अब उपयोग करने योग्य नहीं है।


क्या हम परिभाषित new CountDownLatch(3)से 3 धागे के रूप में उपयोग करते हैं newFixedThreadPool ?
चकलादार असफाक आरिफ

"इससे पहले कि यह प्रसंस्करण जारी रखें" प्रसंस्करण शुरू होने से पहले "नहीं होना चाहिए"?
मारिया इनेस पारनिरी

@Arefe हाँ, यह आपके कोड के ब्लॉक के माध्यम से जाने वाले धागों की संख्या है
विशाल अक्कलकोट

23

निकोलाबी ने इसे बहुत अच्छी तरह से समझाया, हालाँकि उदाहरण समझने में मददगार होगा, इसलिए यहाँ एक सरल उदाहरण है ...

 import java.util.concurrent.*;


  public class CountDownLatchExample {

  public static class ProcessThread implements Runnable {

    CountDownLatch latch;
    long workDuration;
    String name;

    public ProcessThread(String name, CountDownLatch latch, long duration){
        this.name= name;
        this.latch = latch;
        this.workDuration = duration;
    }


    public void run() {
        try {
            System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
            Thread.sleep(workDuration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+ "completed its works");
        //when task finished.. count down the latch count...

        // basically this is same as calling lock object notify(), and object here is latch
        latch.countDown();
    }
}


public static void main(String[] args) {
    // Parent thread creating a latch object
    CountDownLatch latch = new CountDownLatch(3);

    new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
    new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
    new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs


    System.out.println("waiting for Children processes to complete....");
    try {
        //current thread will get notified if all chidren's are done 
        // and thread will resume from wait() mode.
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("All Process Completed....");

    System.out.println("Parent Thread Resuming work....");



     }
  }

22

इसका उपयोग तब किया जाता है जब हम अपने कार्य को पूरा करने के लिए एक से अधिक थ्रेड का इंतजार करना चाहते हैं। यह धागे में शामिल होने के समान है।

जहाँ हम CountDownLatch का उपयोग कर सकते हैं

ऐसे परिदृश्य पर विचार करें जहां हमारी आवश्यकता है जहां हमारे पास तीन धागे "ए", "बी" और "सी" हैं और हम "सी" को केवल "ए" और "बी" धागे शुरू करना चाहते हैं या धागे अपने कार्य को आंशिक रूप से पूरा करते हैं।

इसे वास्तविक विश्व के आईटी परिदृश्य पर लागू किया जा सकता है

ऐसे परिदृश्य पर विचार करें जहां प्रबंधक विकास टीमों (ए और बी) के बीच मॉड्यूल को विभाजित करता है और वह इसे केवल परीक्षण के लिए क्यूए टीम को सौंपना चाहता है जब दोनों टीमें अपना कार्य पूरा करती हैं।

public class Manager {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
        MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
        teamDevA.start();
        teamDevB.start();
        countDownLatch.await();
        MyQATeam qa = new MyQATeam();
        qa.start();
    }   
}

class MyDevTeam extends Thread {   
    CountDownLatch countDownLatch;
    public MyDevTeam (CountDownLatch countDownLatch, String name) {
        super(name);
        this.countDownLatch = countDownLatch;       
    }   
    @Override
    public void run() {
        System.out.println("Task assigned to development team " + Thread.currentThread().getName());
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
                ex.printStackTrace();
        }
    System.out.println("Task finished by development team Thread.currentThread().getName());
            this.countDownLatch.countDown();
    }
}

class MyQATeam extends Thread {   
    @Override
    public void run() {
        System.out.println("Task assigned to QA team");
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("Task finished by QA team");
    }
}

उपरोक्त कोड का आउटपुट होगा:

टास्क विकास टीम को सौंपा गया है

टास्क विकास टीम देव को सौंपा गया

टास्क का विकास टीम देव द्वारा किया गया

टास्क का विकास टीम देव द्वारा किया गया

टास्क क्यूए टीम को सौंपा

टास्क QA टीम द्वारा समाप्त किया गया

यहाँ इंतजार () countdownlatch ध्वज के लिए विधि प्रतीक्षा करता है 0 बनने के लिए, और उलटी गिनती () विधि 1 से countdownlatch झंडा decrements।

JOIN की सीमा: JOIN के साथ उपरोक्त उदाहरण भी प्राप्त किया जा सकता है, लेकिन JOIN का उपयोग दो परिदृश्यों में नहीं किया जा सकता है:

  1. जब हम थ्रेड्स बनाने के लिए थ्रेड क्लास के बजाय ExecutorService का उपयोग करते हैं।
  2. उपरोक्त उदाहरण को संशोधित करें, जहां प्रबंधक क्यूए टीम को कोड सौंपना चाहता है जैसे ही विकास अपना 80% कार्य पूरा करता है। इसका अर्थ है कि काउंटडाउनचैच हमें कार्यान्वयन को संशोधित करने की अनुमति देता है जिसका उपयोग उनके आंशिक निष्पादन के लिए दूसरे धागे की प्रतीक्षा करने के लिए किया जा सकता है।

3

CoundDownLatch आपको एक धागा प्रतीक्षा करने में सक्षम बनाता है जब तक कि अन्य सभी धागे उनके निष्पादन के साथ नहीं किए जाते हैं।

छद्म कोड हो सकता है:

// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution

आप अपने सभी विवरण को कोड ब्लॉक
पॉल लो

हालांकि बेहतरीन टिप्पणी। मुझे ये "सैद्धांतिक रूप से स्पष्टीकरण" के बजाय "टू द पॉइंट" टिप्पणियां पसंद हैं।
रेनताराजोक

2

जावा सिंपल सीरियल कनेक्टर के साथ कुछ इस तरह का उपयोग करने का एक अच्छा उदाहरण है, सीरियल पोर्ट तक पहुंचना। आमतौर पर आप पोर्ट पर कुछ लिखेंगे, और asyncronously, दूसरे धागे पर, डिवाइस एक SerialPortEventListener पर जवाब देगा। आमतौर पर, आप प्रतिक्रिया की प्रतीक्षा करने के लिए पोर्ट पर लिखने के बाद रुकना चाहेंगे। मैन्युअल रूप से इस परिदृश्य के लिए थ्रेड लॉक्स को संभालना बेहद मुश्किल है, लेकिन उलटी गिनती का उपयोग करना आसान है। इससे पहले कि आप सोचें कि आप इसे दूसरे तरीके से कर सकते हैं, दौड़ की परिस्थितियों के बारे में सावधान रहें जो आपने कभी नहीं सोचा था !!

स्यूडोकोड:

CountDownLatch latch;
void writeData() { 
   latch = new CountDownLatch(1);
   serialPort.writeBytes(sb.toString().getBytes())
   try {
      latch.await(4, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
   }
}
class SerialPortReader implements SerialPortEventListener {
    public void serialEvent(SerialPortEvent event) {
        if(event.isRXCHAR()){//If data is available
            byte buffer[] = serialPort.readBytes(event.getEventValue());
            latch.countDown();
         }
     }
}


2

यदि आप अपने कॉल के बाद latch.countDown () में कुछ डिबग जोड़ते हैं, तो इससे आपको इसके व्यवहार को बेहतर ढंग से समझने में मदद मिल सकती है।

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

आउटपुट काउंट को घटाया हुआ दिखाएगा। यह 'काउंट' प्रभावी रूप से रनने योग्य कार्यों (प्रोसेसर ऑब्जेक्ट्स) की संख्या है, जिसके खिलाफ आपने शुरुआत की है, काउंटडाउन () को आमंत्रित नहीं किया गया है और इसलिए latch.await () के लिए इसके कॉल पर मुख्य थ्रेड को ब्लॉक किया गया है।

DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]

2

CountDownLatch के बारे में oracle प्रलेखन से :

एक सिंक्रनाइज़ेशन सहायता जो एक या अधिक थ्रेड्स को प्रतीक्षा करने की अनुमति देती है जब तक कि अन्य थ्रेड्स में किए गए संचालन का एक सेट पूरा नहीं हो जाता।

A CountDownLatchको एक दी गई गणना के साथ आरंभीकृत किया गया है। awaitतरीकों को ब्लॉक जब तक वर्तमान गिनती पहुंच के आमंत्रण के कारण शून्य countDown()और विधि, जिसके बाद सभी इंतजार कर रहे धागे जारी कर रहे हैं तुरंत इंतजार वापसी की किसी भी बाद आमंत्रण। यह एक-शॉट घटना है - गिनती रीसेट नहीं की जा सकती।

एक CountDownLatch एक बहुमुखी तुल्यकालन उपकरण है और इसका उपयोग कई उद्देश्यों के लिए किया जा सकता है।

एक की CountDownLatchएक गिनती के साथ एक प्रारंभिक / बंद कुंडी, या गेट के रूप में कार्य करता है: जब तक यह थ्रेड काउंटडाउन (थ्रेड) द्वारा खोला नहीं जाता, तब तक प्रतीक्षा करने वाले सभी धागे गेट पर प्रतीक्षा करते हैं।

CountDownLatchएन के लिए एक इनिशियलाइज़्ड का उपयोग एक थ्रेड प्रतीक्षा करने के लिए किया जा सकता है जब तक कि एन थ्रेड्स ने कुछ कार्रवाई पूरी नहीं की है, या कुछ एक्शन एन बार पूरा हो चुका है।

public void await()
           throws InterruptedException

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

यदि वर्तमान गिनती शून्य है तो यह विधि तुरंत वापस आती है।

public void countDown()

यदि गिनती शून्य तक पहुँचती है, तो सभी प्रतीक्षा धागे जारी करते हुए, कुंडी की गिनती को घटाता है।

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

अपने उदाहरण की व्याख्या।

  1. आपने latchचर के लिए 3 के रूप में गणना की है

    CountDownLatch latch = new CountDownLatch(3);
  2. आपने इसे latchवर्कर थ्रेड में साझा किया है:Processor

  3. के तीन Runnableउदाहरण Processorप्रस्तुत किए गए हैंExecutorService executor
  4. मुख्य सूत्र ( App) नीचे बयान के साथ शून्य बनने के लिए प्रतीक्षा कर रहा है

     latch.await();  
  5. Processor थ्रेड 3 सेकंड के लिए सोता है और फिर इसके साथ मूल्य में गिरावट आती है latch.countDown()
  6. पहला Processउदाहरण 2 के रूप में लैच काउंट को बदल देगा क्योंकि इसके पूरा होने के बाद latch.countDown()

  7. दूसरा Processउदाहरण इसकी वजह से पूरा होने के बाद लैच काउंट को 1 के रूप में बदल देगा latch.countDown()

  8. तीसरा Processउदाहरण 0 के रूप में पूरा होने के बाद लैच काउंट को बदल देगा latch.countDown()

  9. कुंडी पर शून्य गणना से मुख्य धागा Appनिकलता हैawait

  10. ऐप प्रोग्राम अब इस आउटपुट को प्रिंट करता है: Completed


2

जावा डॉक्टर के इस उदाहरण से मुझे अवधारणाओं को स्पष्ट रूप से समझने में मदद मिली:

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
  }
  public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}

दृश्य व्याख्या:

यहां छवि विवरण दर्ज करें

जाहिर है, CountDownLatchएक धागे (यहां Driver) को चलने के लिए अनुमति देता है जब तक कि चलने वाले धागे (यहां Worker) का एक गुच्छा उनके निष्पादन के साथ नहीं किया जाता है।


1

जैसा कि JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ) में उल्लेख किया गया है , काउंटडाउन लैंथ एक तुल्यकालन सहायता है, जिसे जावा 5 में प्रस्तुत किया गया है। यहाँ सिंक्रनाइज़ेशन नहीं होता है एक महत्वपूर्ण खंड तक पहुंच को सीमित करने का मतलब है। बल्कि विभिन्न धागों की क्रियाओं का अनुक्रमण किया। CountDownLatch के माध्यम से प्राप्त सिंक्रनाइज़ेशन का प्रकार Join के समान है। मान लें कि एक थ्रेड "M" है, जिसे अपने कार्यों को पूरा करने के लिए अन्य वर्कर थ्रेड्स "T1", "T2", "T3" की प्रतीक्षा करनी है। Java 1.5 से पहले, जिस तरह से यह किया जा सकता है, M निम्नलिखित कोड को चला रहा है।

    T1.join();
    T2.join();
    T3.join();

उपरोक्त कोड यह सुनिश्चित करता है कि T1, T2, T3 के बाद थ्रेड एम अपना काम फिर से शुरू करता है। T1, T2, T3 किसी भी क्रम में अपना काम पूरा कर सकते हैं। उसी को काउंटडाउनचैच के माध्यम से प्राप्त किया जा सकता है, जहां टी 1, टी 2, टी 3 और थ्रेड एम समान काउंटडाउन लच वस्तु को साझा करते हैं।
"एम" अनुरोध: countDownLatch.await();
जहां "टी 1", "टी 2", "टी 3" करता है countDownLatch.countdown();

जॉइन मेथड के साथ एक नुकसान यह है कि M को T1, T2, T3 के बारे में जानना होगा। यदि बाद में एक नया वर्कर थ्रेड टी 4 जोड़ा जाता है, तो एम को इसके बारे में भी जागरूक होना होगा। इससे काउंटडाउनचैच से बचा जा सकता है। क्रियान्वयन के बाद कार्रवाई का क्रम [T1, T2, T3] होगा (T1, T2, T3 का क्रम वैसे भी हो सकता है) -> [M]



0
package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.