Як розпочати дві нитки “точно” одночасно


91

Потоки повинні починатися з тієї ж частки секунди. Я розумію, якщо ви це зробите thread1.start(), це займе кілька мілісекунд перед наступним виконанням thread2.start().

Це взагалі можливо чи неможливо?


2
Розділення секунди - це дуже довгий час на швидкості ГГц.
Микола Фетисов

30
Не потрібно голосувати так інтенсивно. Не всі розуміють недетермінізм, пов’язаний з різьбленням, і ми всі повинні десь починати.
Michael Petrotta

7
Не розумію проти. Синхронізація між потоками є дуже поширеною необхідністю. Так, у Java ви не можете виконати їх паралельно (що, на іншій платформі може бути дуже вагомою вимогою, крім того), але час від часу дуже часто потрібно синхронізувати їх дії. Ось чому jdk має класи для цього. Можливо, формулювання було неточним, але біса, якби він це знав, він би не задав питання ..
Енно Шиоджі

ну, мабуть, я розумію весь твій гнів. Це було запитання, яке мені задали в інтерв’ю ... мабуть, це був фокус Q. Але я просто заплутався і хотів це підтвердити. тому я запитав, чи це взагалі можливо.
figaro

2
@javaguy - не "фокусне" питання. Швидше питання, вибране для того, щоб побачити, наскільки добре ви насправді розумієте багатопотокове програмування загалом ... як і у випадку з Java.
Stephen C

Відповіді:


135

Щоб запустити потоки точно в той самий час (принаймні наскільки це можливо), ви можете використовувати CyclicBarrier :

// We want to start just 2 threads at the same time, but let's control that 
// timing from the main thread. That's why we have 3 "parties" instead of 2.
final CyclicBarrier gate = new CyclicBarrier(3);

Thread t1 = new Thread(){
    public void run(){
        gate.await();
        //do stuff    
    }};
Thread t2 = new Thread(){
    public void run(){
        gate.await();
        //do stuff    
    }};

t1.start();
t2.start();

// At this point, t1 and t2 are blocking on the gate. 
// Since we gave "3" as the argument, gate is not opened yet.
// Now if we block on the gate from the main thread, it will open
// and all threads will start to do stuff!

gate.await();
System.out.println("all threads started");

Це не повинно бути a CyclicBarrier, ви також можете використовувати a CountDownLatchабо навіть замок.

Це все ще не може переконатись, що вони запускаються точно одночасно на стандартних JVM, але ви можете наблизитись досить близько. Приблизитися все ще корисно, коли ви проводите, наприклад, тести продуктивності. Наприклад, якщо ви намагаєтеся виміряти пропускну здатність структури даних з різною кількістю потоків, що потрапляють на неї, ви хочете використовувати цей тип конструкції, щоб отримати максимально точний результат.

На інших платформах, починаючи потоки точно можуть бути дуже дійсне вимога до речі.


5
+1 приємне мислення ;-) Хоча, звичайно, це не змушує потоки запускатися в один і той же час у реальному часі (принаймні не на одноядерному чіпі і не гарантується навіть на багатоядерному чіпі), але Я не можу придумати жодного способу зробити краще.
David Z

Чи вдасться це зачепити за специфічні для навколишнього середовища ручки очікування?
ChaosPandion

@ChaosPandion: Хочете детальніше розказати?
Санта

@Santa - Наприклад, Win32 API пропонує різні примітиви. Одним з корисних типів є подія ручного скидання, що повертається під час дзвінка CreateEvent. msdn.microsoft.com/en-us/library/ms686364%28VS.85%29.aspx
ChaosPandion

1
@Zwei - Ну, як би там не було, це відповідь, яку я б написав, якби я був гуру Java.
ChaosPandion

15

Це неможливо, принаймні на одноядерному комп'ютері. Але чому ти цього хочеш? Навіть якщо ви змогли запустити два потоки точно в одну секунду, вони будуть прогресувати по-різному, оскільки планування не залежить від вашого контролю.

Редагувати: (у відповідь на деякі коментарі) Це цілком обґрунтована вимога до синхронізації стану чи прогресу декількох потоків і CyclicBarrierє чудовим інструментом. Я відповів на питання, чи можна запустити кілька потоків одночасно . CyclicBarrierгарантуватиме, що нитки продовжуватимуться, коли вони будуть точно в бажаному стані, але це не гарантує, що вони почнуться або відновляться точно в той же час, хоча це може бути досить близько. У запитанні немає згадки про потреби синхронізації.


1
Ти все-таки можеш наблизитися досить по-чертовски Все залежить від методів синхронізації, які ви використовуєте, і, звичайно, мають більше 1 процесора або ядра.
ChaosPandion

Ідея полягає в тому, що це неправильний підхід і не повинен бути необхідним у будь-якому жорсткому середовищі реального часу, не те, що він "досить близький".
Микола Фетисов

1
Це також неможливо, оскільки планування потоків Java не дає точності в мілісекундах.
Stephen C

1
@Zwei - напевно, ти можеш "проклятись" здебільшого на "простою" машині. Але якщо вам це потрібно постійно, вам потрібно програмувати на такій мові, як C, на машині з жорсткою підтримкою реального часу в ОС. Враховуйте також, що JVM може запускати повний GC у "частку секунди", яку ви хочете запустити.
Stephen C

1
Це цілком обгрунтована проблема синхронізації. Потокам потрібно ініціалізувати деякі дані, переконайтесь, що ніхто не потрапляє у критичну область без належної перевірки. І той факт, що CyclicBerrier включений до одночасного пакету javas, означає, що це важлива проблема.
Денис Тульський

14

Для цього ви можете використовувати CountDownLatch. Нижче наведено зразок. Хоча t1 і t2 запущені, ці потоки продовжують чекати, поки основний потік не відлічить фіксатор. Кількість необхідних зворотних відліків згадується в конструкторі. Засувку зворотного відліку можна також використовувати для очікування завершення потоків, щоб основний потік міг продовжувати далі (зворотний випадок). Цей клас був включений з Java 1.5.

import java.util.concurrent.CountDownLatch;


public class ThreadExample
{
    public static void main(String[] args) 
    {
        CountDownLatch latch = new CountDownLatch(1);
        MyThread t1 = new MyThread(latch);
        MyThread t2 = new MyThread(latch);
        new Thread(t1).start();
        new Thread(t2).start();
        //Do whatever you want
        latch.countDown();          //This will inform all the threads to start
        //Continue to do whatever
    }
}

class MyThread implements Runnable
{
    CountDownLatch latch;
    public MyThread(CountDownLatch latch) 
    {
        this.latch = latch;
    }
    @Override
    public void run() 
    {
        try 
        {
            latch.await();          //The thread keeps waiting till it is informed
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Do the actual thing
    }
}

Ця відповідь може бути корисною завдяки меншій кількості шаблонів у прикладі коду.
user2418306 02

6
  1. Як я розумію, JVM переважно делегує ці матеріали операційній системі. Тож відповідь буде конкретною для ОС.
  2. Це однозначно неможливо на однопроцесорних машинах.
  3. Це складніше щодо багатопроцесорної машини. Згідно з Теорією відносності одночасності , "не можна сказати в абсолютному сенсі, чи відбуваються дві події одночасно, якщо ці події розділені в просторі". Якими б близькими не були ваші процесори, вони розділені в просторі.
    1. Якщо ви можете прийняти відносну одночасність, то, мабуть, простіше просто змоделювати її, використовуючи методи, розглянуті в інших відповідях.

1
... і навіть якщо ми припускаємо одночасний запуск, кожен потік має власну шкалу часу, всі вони є одночасними , але не обов’язково паралельними, тому все, що хтось припускає про одночасність потоків, може (буде) анульовано в наступну наносекунду (будь-якого хронологія) ...
Холгер,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.