Сколько потоков мне действительно нужно? - PullRequest
0 голосов
/ 08 января 2012

Я создаю Java-приложение с графическим интерфейсом и сервером на основе сокетов, и у меня постоянно возникают проблемы с застреванием одной части приложения в ожидании другой (в основном GUI, ожидающей сервера - нетЭто удивляет. Несколько раз мне удается избежать этих ошибок, но вместо этого я обнаруживаю, что мой метод main достигает конца почти мгновенно после запуска. (Приложение может продолжаться или не работать, в зависимости от того, есть ли видимый графический интерфейс илинет, но я думал, что метод main не должен был возвращаться до тех пор, пока программа не завершится ...)

Мои требования к приложению следующие:

  • Он должен иметь возможность обрабатывать неопределенное количество клиентов одновременно
  • Связь между сервером и клиентами может идти в любом направлении, а не обязательно при каждом другом повороте, иногда сервер отправляет кучу сообщений и получает ответы только от некоторыхклиенты, в других случаях все наоборот.
  • Это должно бытьиз-за того, что клиенту «слишком поздно» подключаться - сокет-сервер должен непрерывно принимать подключения в течение всего времени, пока запущено приложение сервера.
  • В течение всего времени сервер не должен изменять графический интерфейсклиенты ждут друг друга.Обновление GUI происходит через прослушиватели событий для других объектов (в основном модели), которые изменяются фоновыми потоками.

Я пробовал следующее, но не могу его получитьright.

  • 1 поток для метода main и "обычная" работа, выполняемая объектами, которые он создает (Controller, Model и т. д.).С этим потоком у меня иногда возникают проблемы, потому что не нигде не удерживается и преждевременно возвращается из main.
  • Используя EventQueue.invokeLater(new Runnable() { ... });, я выполняю все фактические манипуляции с GUI в потоке пользовательского интерфейса, но ни один из этих вызовов не является "выживающими" потоками, поэтому они в основном просто работают асинхронно вне основного потока.
  • 1 поток для ServerSocket, чтобы иметь возможность продолжать прослушивать новые соединения.
  • 1 поток для каждого клиента, чтобы иметь возможность прослушивать сообщения от клиентов.Я не уверен, что мне здесь нужен другой поток, чтобы иметь возможность отправлять сообщения «не в порядке», то есть, не дожидаясь его получения.

Я никогда не писал (реальный) многопоточное приложение раньше, так что это совершенно новое для меня основание.Однако я отказываюсь верить, что эта проблема не была успешно решена раньше - даже так много раз, что развивались какие-то передовые практики.

Что это такое?Что такое хорошая архитектура для этого приложения?

1 Ответ

1 голос
/ 08 января 2012

Существует много разных ответов на этот вопрос, но лучшее правило, которое я могу придумать, это то, что вам нужен один поток пользовательского интерфейса (вы не сказали, что вы используете для графического интерфейса, но вы упомянули invokeLater, поэтому Я думаю, Swing), а затем один или несколько потоков для обработки клиентов. Поток для каждого клиента не нужен; вместо этого используйте java.nio классы для асинхронного ввода-вывода. Возможно, вы захотите сделать общее количество потоков обработки клиента тем, что вы можете настроить во время выполнения; диапазон будет довольно мал, например, от одного до четырех.

Машина, на которой вы запускаете приложение, если это действительно сервер, вероятно, сможет обрабатывать от четырех (например, двухъядерный компьютер) до шестнадцати (четырехъядерных) реальных одновременных потоков выполнения. (очевидно, есть машины серверного класса, у которых даже больше ядер, но вы понимаете), и, конечно, вы делитесь ими со всеми другими службами, работающими в архитектуре. Таким образом, наличие множества потоков приводит к переключению контекста. Переключение контекста дешево, но далеко не бесплатно, и если этого можно избежать, то ЦП может с большей пользой заняться чем-то другим.

В качестве примера серверного приложения, написанного для обработки большого количества клиентов с минимумом потоков, используя NIO, вы можете посмотреть исходный код для Netty . Фактически, вы можете даже взглянуть на простое использование Netty и построение логики приложения на основе обработки ввода-вывода.


Примечание:

Приложение может продолжаться или не запускаться, в зависимости от того, есть ли видимый графический интерфейс или нет, но я, хотя основной метод не должен был возвращаться до тех пор, пока программа не завершится ...

main закончится, как только вы дадите ему закончиться. JVM будет продолжать работать до тех пор, пока не будет запущенных потоков. Если вы хотите, чтобы main ожидал других потоков перед выходом, используйте Thread#join, чтобы присоединиться к ним. join заставляет текущий поток ждать, пока не завершится поток, который вы вызываете join on (некоторые перегрузки join предлагают тайм-аут, поэтому вызывающий поток может возобновить работу, если вызываемый поток не завершается в течение заданного периода времени) , Сравните вывод следующего, когда вы запускаете его без аргументов и запускаете его с аргументом (любой аргумент, содержание аргумента не имеет значения):

public class JoinExample implements Runnable {

    public static final void main(String[] args) {
        Thread t = new Thread(new JoinExample());

        System.out.println("Starting thread");
        t.start();

        if (args.length > 0) {
            System.out.println("Joining thread");
            while (t.isAlive()) {
                try {
                    t.join();
                }
                catch (InterruptedException ie) {
                }
            }
        }

        System.out.println("main exiting");
    }

    public void run() {
        long    stop = System.currentTimeMillis() + 2000;

        System.out.println("Thread starting");
        while (System.currentTimeMillis() < stop) {
            // Sleep a mo
            try {
                Thread.currentThread().sleep(250);
            }
            catch (InterruptedException ie) {
            }
            System.out.println("Thread still running");
        }
        System.out.println("Thread stopping");
    }
}

Все это говорит о том, что вы вполне можете позволить потоку main умереть, поскольку поток пользовательского интерфейса будет потоком диспетчера событий, созданным Swing. Больше информации о темах и качелях здесь и здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...