कैसे पता करें कि अन्य धागे समाप्त हो गए हैं?


126

मेरे पास एक विधि है जिसका नाम है StartDownload(), जिसमें तीन सूत्र हैं।

जब प्रत्येक थ्रेड निष्पादित हो जाता है तो मुझे एक सूचना कैसे मिलती है?

क्या यह जानने का कोई तरीका है कि क्या धागे का एक (या सभी) समाप्त हो गया है या अभी भी चल रहा है?


जवाबों:


228

ऐसे कई तरीके हैं जिनसे आप यह कर सकते हैं:

  1. अपने मुख्य धागे में थ्रेड। जॉइन () का उपयोग करें और प्रत्येक थ्रेड को पूरा करने के लिए एक अवरुद्ध फैशन में प्रतीक्षा करें, या
  2. जाँच करें। मतदान में () एक फैशन में - आम तौर पर हतोत्साहित - जब तक प्रत्येक धागा पूरा हो गया है, या प्रतीक्षा करने के लिए
  3. Unorthodox , प्रत्येक थ्रेड के लिए प्रश्न में, setUncaughtExceptionHandler को अपनी वस्तु में कॉल करने के लिए कॉल करें, और प्रत्येक थ्रेड को अनचाहे अपवाद को फेंकने के लिए प्रोग्राम करें, जब यह पूरा हो जाए, या
  4. Java.util.concurrent , या से ताले या सिंक्रनाइज़र या तंत्र का उपयोग करें
  5. अधिक रूढ़िवादी, अपने मुख्य थ्रेड में एक श्रोता बनाएँ, और फिर अपने प्रत्येक थ्रेड को उस श्रोता को बताने के लिए प्रोग्राम करें जिसे उन्होंने पूरा किया है।

आइडिया # 5 कैसे लागू करें? खैर, एक तरीका यह है कि पहले एक इंटरफ़ेस बनाया जाए:

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

फिर निम्न वर्ग बनाएं:

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

और फिर आपका प्रत्येक थ्रेड विस्तारित होगा NotifyingThreadऔर run()इसे लागू करने के बजाय इसे लागू करेगा doRun()। इस प्रकार जब वे पूर्ण हो जाते हैं, तो वे स्वचालित रूप से अधिसूचना की प्रतीक्षा कर रहे किसी को भी सूचित करेंगे।

अंत में, आपकी मुख्य कक्षा में - वह जो सभी थ्रेड्स शुरू करता है (या कम से कम ऑब्जेक्ट अधिसूचना के लिए इंतजार कर रहा है) - उस क्लास को संशोधित करें implement ThreadCompleteListenerऔर प्रत्येक थ्रेड बनाने के तुरंत बाद श्रोताओं की सूची में खुद को जोड़ें:

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

फिर, जैसा कि प्रत्येक थ्रेड से बाहर निकलता है, आपका notifyOfThreadCompleteतरीका थ्रेड उदाहरण के साथ लागू किया जाएगा जो अभी पूरा हुआ (या दुर्घटनाग्रस्त हो गया)।

ध्यान दें कि बेहतर होगा implements Runnableबजाय extends Threadके लिए NotifyingThreadके रूप में धागा विस्तार आमतौर पर नए कोड में हतोत्साहित किया जाता है। लेकिन मैं आपके सवाल का कोडिंग कर रहा हूं। यदि आप NotifyingThreadकार्यान्वित करने के लिए कक्षा बदलते हैं Runnableतो आपको अपना कुछ कोड बदलना होगा जो थ्रेड्स का प्रबंधन करता है, जो करने के लिए बहुत सरल है।


नमस्ते!! मुझे अंतिम विचार पसंद आया। मैं ऐसा करने के लिए एक श्रोता को लागू करने के लिए? धन्यवाद
रिकार्डो फेलगुइरास

4
लेकिन इस दृष्टिकोण का उपयोग करते हुए, notifiyListeners को रन के अंदर कहा जाता है () इसलिए इसे थ्रेड को insisde कहा जाएगा और आगे की कॉल भी वहीं की जाएगी, क्या यह इस तरह नहीं है?
जोर्डी पुइग्डेलविल 10

1
@ ईडी जोर्डी पूछ रहे थे कि क्या यह संभव है कि notifyविधि को कॉल न किया जाए run, लेकिन इसके बाद।
टॉमाज़ डेज़िएलेव्स्की

1
वास्तव में सवाल यह है: अब आप सेकेंडरी थ्रेड से कैसे हटेंगे । मुझे पता है कि यह समाप्त हो गया है, लेकिन अब मैं मुख्य धागे का उपयोग कैसे करूं ?
पैट्रिक

1
क्या यह धागा सुरक्षित है? ऐसा प्रतीत होता है कि InformListeners (और इस प्रकार InformOfThreadComplete) को सूचना देने वाले थ्रेड के भीतर बुलाया जाएगा, बजाए केवल उस थ्रेड के भीतर जिसने लिंटरनर बनाया था।
आरोन

13

CyclicBarrier का उपयोग कर समाधान

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

लेबल 0 - चक्रीय अवरोध को निष्पादन थ्रेड्स की संख्या के बराबर पार्टियों की संख्या के साथ बनाया गया है, निष्पादन के मुख्य धागे के लिए एक (जिसमें startDownload () निष्पादित किया जा रहा है)

लेबल 1 - n-th DownloadingThread प्रतीक्षा कक्ष में प्रवेश करता है

लेबल 3 - NUMBER_OF_DOWNLOADING_THREADS ने प्रतीक्षालय में प्रवेश किया है। निष्पादन का मुख्य धागा उन्हें कम से कम एक ही समय में अपने डाउनलोडिंग कार्य करना शुरू करने के लिए जारी करता है

लेबल 4 - निष्पादन का मुख्य धागा प्रतीक्षालय में प्रवेश करता है। यह समझने के लिए कोड का 'पेचीदा' हिस्सा है। इससे कोई फर्क नहीं पड़ता कि कौन सा धागा दूसरी बार प्रतीक्षा कक्ष में प्रवेश करेगा। यह महत्वपूर्ण है कि जो भी थ्रेड कमरे में प्रवेश करता है वह अंतिम सुनिश्चित करता है कि अन्य सभी डाउनलोडिंग थ्रेड ने अपनी डाउनलोडिंग नौकरियां समाप्त कर ली हैं।

लेबल 2 - n-th DownloadingThread ने अपना डाउनलोडिंग कार्य पूरा कर लिया है और प्रतीक्षा कक्ष में प्रवेश करता है। यदि यह अंतिम एक है या पहले से ही NUMBER_OF_DOWNLOADING_THREADS ने इसे दर्ज किया है, जिसमें निष्पादन का मुख्य धागा शामिल है, मुख्य धागा केवल तब ही इसका निष्पादन जारी रखेगा, जब अन्य सभी धागे डाउनलोड करना समाप्त कर देंगे।


9

आपको वास्तव में एक समाधान का उपयोग करना चाहिए java.util.concurrent। विषय पर जोश बलोच और / या ब्रायन गोएट्ज को खोजें और पढ़ें।

यदि आप उपयोग नहीं कर java.util.concurrent.*रहे हैं और सीधे थ्रेड्स का उपयोग करने की जिम्मेदारी ले रहे हैं, तो आपको संभवतः join()यह जानने के लिए उपयोग करना चाहिए कि थ्रेड कब किया जाता है। यहाँ एक सुपर सरल कॉलबैक तंत्र है। Runnableकॉलबैक करने के लिए पहले इंटरफ़ेस का विस्तार करें :

public interface CallbackRunnable extends Runnable {
    public void callback();
}

फिर एक एग्जिक्युटिव बनाइए जो आपके रन करने योग्य को निष्पादित करेगा और जब यह हो जाएगा तब आपको कॉल करेगा।

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

आपके CallbackRunnableइंटरफ़ेस में जोड़ने के लिए अन्य प्रकार की स्पष्ट चीज़ किसी भी अपवाद को संभालने का एक साधन है, इसलिए हो सकता है कि public void uncaughtException(Throwable e);वहाँ एक लाइन डालें और आपके निष्पादक में, उस इंटरफ़ेस विधि को भेजने के लिए एक Thread.UncaughtExceptionHandler स्थापित करें।

लेकिन वह सब करना वास्तव में जैसे बदबू करने लगता है java.util.concurrent.Callablejava.util.concurrentयदि आपकी परियोजना इसकी अनुमति देती है तो आपको वास्तव में इसका उपयोग करना चाहिए ।


मैं इस बात पर थोड़ा स्पष्ट नहीं हूं कि आप इस कॉलबैक मैकेनिज्म बनाम केवल कॉलिंग से क्या हासिल करते हैं runner.join()और फिर उसके बाद जो भी कोड आप चाहते हैं, जब से आप जानते हैं कि धागा समाप्त हो गया है। क्या यह सिर्फ इतना है कि आपको उस कोड को रन करने योग्य की संपत्ति के रूप में परिभाषित करना है, इसलिए आपके पास अलग-अलग रनवे के लिए अलग-अलग चीजें हो सकती हैं?
स्टीफन

2
हां, runner.join()इंतजार करने का सबसे सीधा तरीका है। मैं मान रहा था कि ओपी अपने मुख्य कॉलिंग थ्रेड को ब्लॉक नहीं करना चाहता था क्योंकि वे प्रत्येक डाउनलोड के लिए "अधिसूचित" होने को कहते थे, जो किसी भी क्रम में पूरा हो सकता था। इसने अतुल्यकालिक रूप से अधिसूचित होने का एक तरीका पेश किया।
ब्रूसेसेब

4

क्या आप उन्हें खत्म करने के लिए इंतजार करना चाहते हैं? यदि हां, तो जॉइन विधि का उपयोग करें।

यदि आप इसे बस चेक करना चाहते हैं, तो इसमें अलग-अलग संपत्ति भी है।


3
ध्यान दें कि यदि कोई थ्रेड अभी तक निष्पादित नहीं करना शुरू करता है (भले ही आपका खुद का थ्रेड उस पर स्टार्ट कह चुका हो) तो झूठी रिटर्न देता है।
टॉम हॉकिन -

@ टॉमहॉटिन-टैक्लाइन क्या आप इस बारे में निश्चित हैं? यह जावा दस्तावेज़ीकरण का विरोध करेगा ("एक धागा जीवित है अगर इसे शुरू किया गया है और अभी तक मर नहीं गया है" - docs.oracle.com/javase/6/docs/api/java/lang/… )। यह यहां के जवाबों का भी खंडन करेगा ( stackoverflow.com/questions/17293304/… )
स्टीफन

@ स्टेफेन एक लंबे समय के बाद से मैंने लिखा है, लेकिन यह सच लगता है। मुझे लगता है कि यह नौ साल पहले मेरी याददाश्त में ताज़ा अन्य लोगों की समस्याओं का कारण बना। वास्तव में जो देखने योग्य है वह कार्यान्वयन पर निर्भर करेगा। आप एक Threadको बताते हैं start, जो थ्रेड करता है, लेकिन कॉल तुरंत लौटता है। isAliveएक साधारण ध्वज परीक्षण होना चाहिए, लेकिन जब मैंने इसे देखा तो यह विधि थी native
टॉम हॉल्टिन -

4

आप getState () के साथ थ्रेड इंस्टेंस पर पूछताछ कर सकते हैं, जो थ्रेड की एक आवृत्ति लौटाता है। निम्नलिखित मूल्यों में से एक के साथ एन्यूमरेशन करें:

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

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


3 बच्चों के बाहर निकलने का इंतजार उपयोग प्रतिमान में फिट नहीं हो सकता है। यदि यह एक डाउनलोड प्रबंधक है, तो वे 15 डाउनलोड शुरू करना चाहते हैं और बस स्टेटस बार से स्थिति को हटा सकते हैं या डाउनलोड पूरा होने पर उपयोगकर्ता को सचेत कर सकते हैं, इस स्थिति में कॉलबैक बेहतर काम करेगा।
डिजिटलजेल

3

आप ExecutorService थ्रेड पूल Executorsबनाने के लिए ऑब्जेक्ट का उपयोग भी कर सकते हैं । फिर अपने प्रत्येक धागे को चलाने के लिए और फ्यूचर्स को पुनः प्राप्त करने के लिए विधि का उपयोग करें । यह तब तक अवरुद्ध होगा जब तक सभी निष्पादन समाप्त नहीं हो जाते। आपका दूसरा विकल्प पूल का उपयोग करके प्रत्येक को निष्पादित करना होगा और तब तक ब्लॉक को कॉल करना होगा जब तक कि पूल निष्पादित न हो जाए। जब आप कार्य जोड़ रहे हों तो बस कॉल करना सुनिश्चित करें ()।invokeAllawaitTerminationshutdown


2

मल्टी थ्रेडिंग मोर्चे पर पिछले 6 सालों में कई चीजें बदली गई हैं।

join()API का उपयोग करने और लॉक करने के बजाय , आप उपयोग कर सकते हैं

1. एक्ज़ीक्यूसर सर्विस invokeAll() एपीआई

दिए गए कार्यों को निष्पादित करता है, सभी पूर्ण होने पर अपनी स्थिति और परिणामों को पकड़े हुए फ्यूचर्स की सूची लौटाते हैं।

2. काउंटडाउनचैट

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

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

3. ForkJoinPool या newWorkStealingPool()में निष्पादकों दूसरा रास्ता है

4. सभी Futureकार्यों के माध्यम से सबमिट करें ExecutorServiceऔर ऑब्जेक्ट get()पर अवरुद्ध कॉल के साथ स्थिति की जांच करेंFuture

संबंधित एसई प्रश्नों पर एक नजर:

उस धागे की प्रतीक्षा कैसे करें जो यह स्वयं का धागा है?

निष्पादनकर्ता: यदि कार्यों को पुनरावर्ती बनाया जाता है, तो सभी कार्यों को पूरा होने तक सिंक्रनाइज़ करने के लिए कैसे प्रतीक्षा करें?


2

मैं सुझाव दूंगा कि थ्रेड क्लास के लिए जावदोक देखना ।

धागा हेरफेर के लिए आपके पास कई तंत्र हैं।

  • आपका मुख्य धागा join()तीन धागे को क्रमिक रूप से ले सकता है, और तब तक आगे नहीं बढ़ेगा जब तक कि तीनों नहीं हो जाते।

  • अंतराल पर थ्रेडेड थ्रेड्स की थ्रेड स्थिति को पोल करें।

  • सभी थ्रेडेड थ्रेड्स को एक अलग में रखें ThreadGroupऔर उस activeCount()पर पोल करें ThreadGroupऔर 0 पर जाने के लिए प्रतीक्षा करें।

  • अंतर-थ्रेड संचार के लिए एक कस्टम कॉलबैक या श्रोता प्रकार के इंटरफ़ेस को सेट करें।

मुझे यकीन है कि अन्य बहुत सारे तरीके हैं जो मुझे अभी भी याद हैं।


1

यहाँ एक समाधान है जो सरल, संक्षिप्त, समझने में आसान है, और मेरे लिए पूरी तरह से काम करता है। जब एक और धागा समाप्त होता है तो मुझे स्क्रीन पर आकर्षित होने की आवश्यकता होती है; लेकिन नहीं कर सकता क्योंकि मुख्य धागे में स्क्रीन का नियंत्रण है। इसलिए:

(1) मैंने वैश्विक वैरिएबल बनाया है: boolean end1 = false;थ्रेड अंत होने पर इसे सही पर सेट करता है। यह "पोस्टडेलिड" लूप द्वारा मुख्य स्थान पर उठाया जाता है, जहां इसका जवाब दिया जाता है।

(2) मेरे धागे में शामिल हैं:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) सौभाग्य से, "पोस्टडेलिड" मुख्य धागे में चलता है, इसलिए यहीं से हर एक बार दूसरे धागे की जाँच करें। जब दूसरा धागा समाप्त होता है, तो यह वह शुरू कर सकता है जो हम आगे करना चाहते हैं।

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(४) अंत में, कॉल करके अपने कोड में कहीं चलने वाली पूरी चीज़ शुरू करें:

void startThread()
{
   myThread();
   checkThread();
}

1

मुझे लगता है कि ThreadPoolExecutorकक्षा का उपयोग करने का सबसे आसान तरीका है ।

  1. इसकी एक कतार है और आप सेट कर सकते हैं कि कितने धागे समानांतर में काम करना चाहिए।
  2. इसमें कॉलबैक के अच्छे तरीके हैं:

हुक के तरीके

यह वर्ग संरक्षित अधिलेखनीय beforeExecute(java.lang.Thread, java.lang.Runnable)और afterExecute(java.lang.Runnable, java.lang.Throwable)तरीके प्रदान करता है जिन्हें प्रत्येक कार्य के निष्पादन से पहले और बाद में कहा जाता है। इनका उपयोग निष्पादन पर्यावरण में हेरफेर करने के लिए किया जा सकता है; उदाहरण के लिए, थ्रेडलोकल्स को फिर से संगठित करना, आंकड़े इकट्ठा करना, या लॉग प्रविष्टियों को जोड़ना। इसके अतिरिक्त, terminated()किसी भी विशेष प्रसंस्करण को निष्पादित करने के लिए विधि को ओवरराइड किया जा सकता है जिसे एक्जिक्यूटर के पूरी तरह से समाप्त हो जाने के बाद करने की आवश्यकता होती है।

जो ठीक वैसा ही है जैसा हमें चाहिए। हम afterExecute()प्रत्येक थ्रेड के बाद कॉलबैक प्राप्त करने के लिए ओवरराइड करेंगे और यह terminated()जानने के लिए ओवरराइड करेंगे कि सभी थ्रेड्स कब किए जाते हैं।

तो यहाँ है कि आपको क्या करना चाहिए

  1. एक निष्पादक बनाएं:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. और अपने सूत्र शुरू करें:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

informUiThatWeAreDone();जब सभी थ्रेड्स किए जाते हैं, तो अंदर की विधि जो भी करना है, उदाहरण के लिए, यूआई को अपडेट करें।

नोट:synchronized यदि आप synchronizedकिसी अन्य synchronizedविधि से कॉल करने का निर्णय लेते हैं, तो समानांतर और BE VERY CAUTIOUS में अपना काम करने के बाद से विधियों का उपयोग करना न भूलें ! यह अक्सर गतिरोध की ओर जाता है

उम्मीद है की यह मदद करेगा!


0

आप SwingWorker का उपयोग भी कर सकते हैं, जिसमें अंतर्निहित संपत्ति परिवर्तन समर्थन है। देखें addPropertyChangeListener () या प्राप्त () एक राज्य परिवर्तन श्रोता उदाहरण के लिए विधि।


0

थ्रेड वर्ग के लिए जावा दस्तावेज़ देखें। आप थ्रेड की स्थिति की जांच कर सकते हैं। यदि आप तीन थ्रेड्स को सदस्य चर में रखते हैं, तो सभी तीन थ्रेड एक दूसरे के राज्यों को पढ़ सकते हैं।

आपको थोड़ा सावधान रहना होगा, हालांकि, क्योंकि आप थ्रेड्स के बीच दौड़ की स्थिति पैदा कर सकते हैं। बस दूसरे थ्रेड्स की स्थिति के आधार पर जटिल तर्क से बचने की कोशिश करें। निश्चित रूप से एक ही चर के लिए कई सूत्र लिखने से बचें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.