Thread’ом java не испортишь: часть v

Методы wait и notify

Последнее обновление: 27.04.2018

Иногда при взаимодействии потоков встает вопрос о извещении одних потоков о действиях других. Например, действия одного потока зависят от результата действий другого потока,
и надо как-то известить один поток, что второй поток произвел некую работу. И для подобных ситуаций у класса Object определено ряд методов:

  • wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод

  • notify(): продолжает работу потока, у которого ранее был вызван метод

  • notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод

Все эти методы вызываются только из синхронизированного контекста — синхронизированного блока или метода.

Рассмотрим, как мы можем использовать эти методы. Возьмем стандартную задачу из прошлой темы — «Производитель-Потребитель» («Producer-Consumer»):
пока производитель не произвел продукт, потребитель не может его купить. Пусть производитель должен произвести 5 товаров, соответственно потребитель
должен их все купить. Но при этом одновременно на складе может находиться не более 3 товаров.
Для решения этой задачи задействуем методы и :

public class Program {
 
    public static void main(String[] args) {
         
        Store store=new Store();
        Producer producer = new Producer(store);
        Consumer consumer = new Consumer(store);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
// Класс Магазин, хранящий произведенные товары
class Store{
   private int product=0;
   public synchronized void get() {
      while (product<1) {
         try {
            wait();
         }
         catch (InterruptedException e) {
         }
      }
      product--;
      System.out.println("Покупатель купил 1 товар");
      System.out.println("Товаров на складе: " + product);
      notify();
   }
   public synchronized void put() {
       while (product>=3) {
         try {
            wait();
         }
         catch (InterruptedException e) { 
         } 
      }
      product++;
      System.out.println("Производитель добавил 1 товар");
      System.out.println("Товаров на складе: " + product);
      notify();
   }
}
// класс Производитель
class Producer implements Runnable{
 
    Store store;
    Producer(Store store){
       this.store=store; 
    }
    public void run(){
        for (int i = 1; i < 6; i++) {
            store.put();
        }
    }
}
// Класс Потребитель
class Consumer implements Runnable{
     
     Store store;
    Consumer(Store store){
       this.store=store; 
    }
    public void run(){
        for (int i = 1; i < 6; i++) {
            store.get();
        }
    }
}

Итак, здесь определен класс магазина, потребителя и покупателя. Производитель в методе добавляет в объект Store с помощью его метода
6 товаров. Потребитель в методе в цикле обращается к методу объекта Store для получения
этих товаров. Оба метода Store — и являются синхронизированными.

Для отслеживания наличия товаров в классе Store проверяем значение переменной . По умолчанию товара нет, поэтому переменная равна .
Метод — получение товара должен срабатывать только при наличии хотя бы одного товара. Поэтому в методе
проверяем, отсутствует ли товар:

while (product<1)

Если товар отсутсвует, вызывается метод . Этот метод освобождает монитор объекта Store и блокирует выполнение метода get, пока для этого же монитора не будет вызван
метод .

Когда в методе добавляется товар и вызывается , то метод получает монитор и выходит из
конструкции , так как товар добавлен. Затем имитируется получение покупателем товара. Для этого
выводится сообщение, и уменьшается значение product: . И в конце вызов метода дает сигнал методу продолжить работу.

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

И теперь программа покажет нам другие результаты:

Производитель добавил 1 товар
Товаров на складе: 1
Производитель добавил 1 товар
Товаров на складе: 2
Производитель добавил 1 товар
Товаров на складе: 3
Покупатель купил 1 товар
Товаров на складе: 2
Покупатель купил 1 товар
Товаров на складе: 1
Покупатель купил 1 товар
Товаров на складе: 0
Производитель добавил 1 товар
Товаров на складе: 1
Производитель добавил 1 товар
Товаров на складе: 2
Покупатель купил 1 товар
Товаров на складе: 1
Покупатель купил 1 товар
Товаров на складе: 0

Таким образом, с помощью в методе мы ожидаем, когда производитель добавит новый продукт. А после добавления
вызываем , как бы говоря, что на складе освободилось одно место, и можно еще добавлять.

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

НазадВперед

Дополнительные материалы

Чтение

  • Блог Алексея Шипилёва — знаю, что очевидно, но просто грех не упомянуть
  • Блог Черемина Руслана — последнее время не пишет активно, нужно искать в блоге его старые записи, поверьте это стоит того — там кладезь
  • Хабр Глеба Смирнова — есть отличные статьи про многопоточность и модель памяти
  • Блог Романа Елизарова — заброшен, но археологические раскопки провести нужно. В целом Роман очень много сделал для просветления народа в области теории многопоточного программирования, ищите его в медиа.

Подкасты

  • SDCast #62: в гостях Александр Титов и Амир Аюпов, инженеры из Intel и Алексей Маркин, программист из МЦСТ
  • SDCast #63: в гостях Алексей Маркин, программист из МЦСТ
  • Разбор Полетов: #107 Истории альпинистов
  • Разбор Полетов: #154 Кишочки — Атака на Новый Год

Видео

  • Computer Science Center — Лекция 11. Модели памяти и проблемы видимости
  • Теория и практика многопоточного программирования

Пример создания потока. Наследуем класс Thread

Мы можем наследовать класс  для создания собственного класса Thread и переопределить метод . Тогда мы можем создать экземпляр этого класса и вызвать метод  для того, чтобы выполнить метод .

Вот простой пример того, как наследоваться от класса Thread:

Java

package ua.com.prologistic;

public class MyThread extends Thread {

public MyThread(String name) {
super(name);
}

@Override
public void run() {
System.out.println(«Стартуем наш поток » + Thread.currentThread().getName());
try {
Thread.sleep(1000);
// для примера будем выполнять обработку базы данных
doDBProcessing();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(«Заканчиваем наш поток » + Thread.currentThread().getName());
}
// метод псевдообработки базы данных
private void doDBProcessing() throws InterruptedException {
Thread.sleep(5000);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

packageua.com.prologistic;

publicclassMyThreadextendsThread{

publicMyThread(Stringname){

super(name);

}

@Override

publicvoidrun(){

System.out.println(«Стартуем наш поток «+Thread.currentThread().getName());

try{

Thread.sleep(1000);

// для примера будем выполнять обработку базы данных

doDBProcessing();

}catch(InterruptedExceptione){

e.printStackTrace();

}

System.out.println(«Заканчиваем наш поток «+Thread.currentThread().getName());

}

// метод псевдообработки базы данных

privatevoiddoDBProcessing()throwsInterruptedException{

Thread.sleep(5000);

}

}

Вот тестовая программа, показывающая наш поток в работе:

Java

package ua.com.prologistic;

public class ThreadRunExample {

public static void main(String[] args){
Thread t1 = new Thread(new HeavyWorkRunnable(), «t1»);
Thread t2 = new Thread(new HeavyWorkRunnable(), «t2»);
System.out.println(«Стартуем runnable потоки»);
t1.start();
t2.start();
System.out.println(«Runnable потоки в работе»);
Thread t3 = new MyThread(«t3»);
Thread t4 = new MyThread(«t4»);
System.out.println(«Стартуем наши кастомные потоки»);
t3.start();
t4.start();
System.out.println(«Кастомные потоки в работе»);

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

packageua.com.prologistic;

publicclassThreadRunExample{

publicstaticvoidmain(Stringargs){

Thread t1=newThread(newHeavyWorkRunnable(),»t1″);

Thread t2=newThread(newHeavyWorkRunnable(),»t2″);

System.out.println(«Стартуем runnable потоки»);

t1.start();

t2.start();

System.out.println(«Runnable потоки в работе»);

Thread t3=newMyThread(«t3»);

Thread t4=newMyThread(«t4»);

System.out.println(«Стартуем наши кастомные потоки»);

t3.start();

t4.start();

System.out.println(«Кастомные потоки в работе»);

}

}

Создание потока с интерфейсом Runnable

Есть более сложный вариант создания потока. Для создания нового потока нужно реализовать интерфейс Runnable. Вы можете создать поток из любого объекта, реализующего интерфейс Runnable и объявить метод run().

Внутри метода run() вы размещаете код для нового потока. Этот поток завершится, когда метод вернёт управление.

Когда вы объявите новый класс с интерфейсом Runnable, вам нужно использовать конструктор:

В первом параметре указывается экземпляр класса, реализующего интерфейс. Он определяет, где начнётся выполнение потока. Во втором параметре передаётся имя потока.

После создания нового потока, его нужно запустить с помощью метода start(), который, по сути, выполняет вызов метода run().

Создадим новый поток внутри учебного проекта в виде вложенного класса и запустим его.

Внутри конструктора MyRunnable() мы создаём новый объект класса Thread

thread = new Thread(this, "Поток для примера");

В первом параметре использовался объект this, что означает желание вызвать метод run() этого объекта. Далее вызывается метод start(), в результате чего запускается выполнение потока, начиная с метода run(). В свою очередь метод запускает цикл для нашего потока. После вызова метода start(), конструктор MyRunnable() возвращает управление приложению. Когда главный поток продолжает свою работу, он входит в свой цикл. После этого оба потока выполняются параллельно.

Можно запускать несколько потоков, а не только второй поток в дополнение к первому. Это может привести к проблемам, когда два потока пытаюсь работать с одной переменной одновременно.

Как выбирать производителя твердотельных накопителей

Looper

Поток имеет в своём составе сущности Looper, Handler, MessageQueue.

Каждый поток имеет один уникальный Looper и может иметь много Handler.

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

Поток получает свой Looper и MessageQueue через метод Looper.prepare() после запуска. Looper.prepare() идентифицирует вызывающий потк, создаёт Looper и MessageQueue и связывает поток с ними в хранилище ThreadLocal. Метод Looper.loop() следует вызывать для запуска Looper. Завершить его работу можно через метод looper.quit().

Используйте статический метод getMainLooper() для доступа к Looper главного потока:

Создадим два потока. Один запустим в основном потоке, а второй отдельно от основного. Нам будет достаточно двух кнопок и метки.

Обратите внимание, как запускаются потоки. Первый поток запускается с помощью метода start(), а второй — run()

Затем проверяем, в каком потоке мы находимся.

Эта тема достаточно сложная и для большинства не представляет интереса и необходимости изучать.

В Android потоки в чистом виде используются всё реже и реже, у системы есть собственные способы.

Остались вопросы? Бесплатная консультация по телефону:

Нет необходимости писать свое

Даг Ли создал отличную открытую библиотеку утилит параллельности, , которая включает объекты-мьютексы, семафоры, коллекции, такие как очереди и хэш-таблицы, хорошо работающие при параллельном доступе, и несколько реализаций рабочей очереди. Класс из этого пакета — эффективная, широко использующаяся, правильная реализация пула потоков, основанного на рабочей очереди. Прежде чем пытаться писать собственное программное обеспечение, которое вполне может оказаться неправильным, вы можете рассмотреть использование некоторых утилит в . Ссылки и дополнительную информацию смотрите в разделе .

Библиотека также служит вдохновителем для JSR 166, рабочей группы Java Community Process (JCP), которая будет производить набор параллельных утилит для включения в библиотеку классов Java в пакете , и которая готовит выпуск Java Development Kit 1.5.

Запуск задач с помощью java.util.concurrent.ExecutorService

Облегчив с помощью интерфейса Callable создание задач для параллельного выполнения, пакет java.util.concurrent также берет на себя работу по запуску и остановке потоков. Вместо объекта Thread предлагается использовать объект типа ExecutorService, с помощью которого пользователь может просто поместить задачу в очередь на выполнение и ждать получения результата. Можно сказать, что ExecutorService – это значительно усовершенствованная реализация шаблона WorkerThread.

ExecutorService – это интерфейс, поэтому для выполнения задач используются его конкретные потомки, адаптированные под требования разрабатываемого приложения. Однако программисту нет необходимости создавать собственную реализацию ExecutorService, так как в пакете java.util.concurrent уже присутствуют различные варианты реализации ExecutorService. Доступ к ним можно получить через статические методы служебного класса Executors, метод которого newFixedThreadPool возвращает объект типа ExecutorService со встроенной поддержкой шаблона ThreadPool. Также в классе Executors есть и другие методы для создания объектов ExecutorService с различными свойствами.

Наибольший интерес в ExecutorService представляет метод submit, через который задача ставится в очередь на выполнение. На вход этот метод принимает объект типа Callable или Runnable, а возвращает некий параметризованный объект типа Future. Этот объект можно использовать для доступа к результату выполнения задачи, который будет возвращен из метода call соответствующего Callable-объекта. При этом через объект Future можно проверить, закончено ли уже выполнение задачи – с помощью метода isDone и через метод get получить доступ к результату или исключительной ситуации, если в процессе выполнения задачи произошла ошибка.

Таким образом, при запуске задач с помощью классов из пакета java.util.concurrent не требуется прибегать к низкоуровневой поточной функциональности класса Thread, достаточно создать объект типа ExecutorService с нужными свойствами и передать ему на исполнение задачу типа Callable. Впоследствии можно легко просмотреть результат выполнения этой задачи с помощью объекта Future, как показано в листинге 4.

Листинг 4. Запуск задачи с помощью классов пакета java.util.concurrent
1 public class ExecutorServiceSample {
2     public static void main(String[] args) {
3         //создать ExecutorService на базе пула из пяти потоков
4         ExecutorService es1 = Executors.newFixedThreadPool(5);
5         //поместить задачу в очередь на выполнение
6         Future<String> f1 = es1.submit(new CallableSample());        
7         while(!f1.isDone()) {
8             //подождать пока задача не выполнится
9         }
10        try {
11            //получить результат выполнения задачи
12            System.out.println("task has been completed : " + f1.get());
13        } catch (InterruptedException ie) {           
14            ie.printStackTrace(System.err);
15        } catch (ExecutionException ee) {
16            ee.printStackTrace(System.err);
17        }
18        es1.shutdown();
19    }
20}

Стоит обратить внимание на строку 18, где происходит остановка объекта ExecutorService с помощью метода shutdown. Дело в том, что потоки в объекте ExecutorService не останавливаются сами, как обычно, поэтому их необходимо явно остановить с помощью этого метода, при этом если в ExecutorService находятся невыполненные задачи, то потоки будут остановлены только, когда завершится последняя задача

Java Thread Join(). Теория

. Этот метод приостановит выполнение текущего потока до тех пор, пока другой поток не закончит свое выполнение. Если поток прерывается, бросается .

: Этот метод приостановит выполнение текущего потока на указанное время в миллисекундах. Выполнение этого метода зависит от реализации ОС, поэтому Java не гарантирует, что текущий поток будет ждать указанное вами время.

: Этот метод приостановит выполнение текущего потока до тех пор, пока другой поток не закончит свое выполнение на время заданное в миллисекундах плюс наносекундах.

Вот простой пример, показывающий использование метода . Цель программы: убедиться в том, что третий поток начнет работу только тогда, когда первый закончит выполнение.

Java

package ua.com.prologistic;

public class ThreadJoinExample {

public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), «t1»);
Thread t2 = new Thread(new MyRunnable(), «t2»);
Thread t3 = new Thread(new MyRunnable(), «t3»);

t1.start();

//стартуем второй поток только после 2-секундного ожидания первого потока (или когда он умрет/закончит выполнение)
try {
t1.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

t2.start();

//стартуем 3-й поток только после того, как 1 поток закончит свое выполнение
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

t3.start();

//даем всем потокам возможность закончить выполнение перед тем, как программа (главный поток) закончит свое выполнение
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(«Все потоки отработали, завершаем программу»);
}

}

class MyRunnable implements Runnable{

@Override
public void run() {
System.out.println(«Поток начал работу:::» + Thread.currentThread().getName());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(«Поток отработал:::» + Thread.currentThread().getName());
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

packageua.com.prologistic;

publicclassThreadJoinExample{

publicstaticvoidmain(Stringargs){

Thread t1=newThread(newMyRunnable(),»t1″);

Thread t2=newThread(newMyRunnable(),»t2″);

Thread t3=newThread(newMyRunnable(),»t3″);

t1.start();

//стартуем второй поток только после 2-секундного ожидания первого потока (или когда он умрет/закончит выполнение)

try{

t1.join(2000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

t2.start();

//стартуем 3-й поток только после того, как 1 поток закончит свое выполнение

try{

t1.join();

}catch(InterruptedExceptione){

e.printStackTrace();

}

t3.start();

//даем всем потокам возможность закончить выполнение перед тем, как программа (главный поток) закончит свое выполнение

try{

t1.join();

t2.join();

t3.join();

}catch(InterruptedExceptione){

e.printStackTrace();

}

System.out.println(«Все потоки отработали, завершаем программу»);

}

}

classMyRunnableimplementsRunnable{

@Override

publicvoidrun(){

System.out.println(«Поток начал работу:::»+Thread.currentThread().getName());

try{

Thread.sleep(4000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

System.out.println(«Поток отработал:::»+Thread.currentThread().getName());

}

}

Результат выполнения программы:

Java

Поток начал работу:::t1
Поток начал работу:::t2
Поток отработал:::t1
Поток начал работу:::t3
Поток отработал:::t2
Поток отработал:::t3
Все потоки отработали, завершаем программу

1
2
3
4
5
6
7

Потокначалработу::t1

Потокначалработу::t2

Потокотработал::t1

Потокначалработу::t3

Потокотработал::t2

Потокотработал::t3

Всепотокиотработали,завершаемпрограмму

Следите за обновлениями раздела Многопоточность и параллелизм в Java

Новые возможности пакета java.uti.concurrent

Платформа Java постоянно развивается, и поэтому к существующей функциональности все время добавляются новые возможности. Иногда новая функциональность берется из уже существующих сторонних библиотек, при этом речь не идет о банальном копировании, а скорее о переосмыслении и доработке уже существующих решений. Подобным способом в версию Java 5 был добавлен пакет java.util.concurrent, включающий в себя множество уже проверенных и хорошо зарекомендовавших себя приемов для параллельного выполнения задач (этот пакет — только одно из множества важных нововведений, представленных в Java 5).

В рамках этой статьи интерес представляют уже готовые к использованию реализации шаблонов WorkerThread и ThreadPool, а также еще один способ реализации задач для параллельного выполнения, кроме упоминавшихся класса Thread и интерфейса Runnable. Ещё в пакете java.util.concurrent находятся два подпакета: java.util.concurrent.locks и java.util.concurrent.atomic, с которыми тоже стоит ознакомиться, так как они значительно упрощают организацию взаимодействия между потоками и параллельного доступа к данным.

2 Класс InputStream

Класс интересен тем, что является классом-родителем для сотен классов-наследников. В нем самом нет никаких данных, однако у него есть методы, которые есть у всех его классов-наследников.

Объекты-потоки вообще редко хранят в себе данные. Поток — это инструмент чтения/записи данных, но не хранения. Хотя бывают и исключения.

Методы класса и всех его классов-наследников:

Методы Описание
Читает один байт из потока
Читает массив байт из потока
Читает все байты из потока
Пропускает байт в потоке (читает и выкидывает)
Проверяет, сколько байт еще осталось в потоке
Закрывает поток

Вкратце пройдемся по этим методам:

Метод

Метод читает один байт из потока и возвращает его. Вас может сбить тип результата — , однако так было сделано, потому что тип — это стандарт всех целых чисел. Три первые байта типа будут равны нулю.

Метод

Это вторая модификация метода . Он позволяет считать из сразу массив байт. Массив для сохранения байт нужно передать в качестве параметра. Метод возвращает число — количество реально прочитанных байт.

Допустим у вас буфер на 10 килобайт, и вы читаете данные из файла с помощью класса . Если файл содержит всего 2 килобайта, все данные будут помещены в массив-буфер, а метод вернет число 2048 (2 килобайта).

Метод

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

Метод

Этот метод позволяет пропустить n первых байт из объекта . Поскольку данные читаются строго последовательно, этот метод просто вычитывает n первых байт из потока и выбрасывает их.

Возвращает число байт, которые были реально пропущены (если поток закончился раньше, чем прокрутили байт).

Метод

Метод возвращает количество байт, которое еще осталось в потоке

Метод

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

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

Код Примечание
для чтения из файла для записи в файл
Буфер, в который будем считывать данные
Пока данные есть в потоке
Считываем данные в буфер
Записываем данные из буфера во второй поток

В этом примере мы использовали два класса: — наследник для чтения данных из файла, и класс — наследник для записи данных в файл. О втором классе расскажем немного позднее.

Еще один интересный момент — это переменная . Когда из файла будет читаться последний блок данных, легко может оказаться, что его длина меньше 64Кб. Поэтому в output нужно тоже записать не весь буфер, а только его часть: первые байт. Именно это и делается в методе .

1 Нововведения в Java 8: Функциональное программирование

Вместе с выходом Java 8 в ней появилась мощная поддержка функционального программирования. Можно даже сказать, долгожданная поддержка функционального программирования. Код стал писаться быстрее, хотя читать его стало сложнее

Перед изучением функционального программирования в Java, рекомендуем хорошо разобраться в трех вещах:

  1. ООП, наследование и интерфейсы (1-2 уровни квеста Java Core).
  2. Дефолтная реализация методов в интерфейсе.
  3. Внутренние и анонимные классы.

Хорошая новость заключается в том, что без знания всего этого можно пользоваться многими возможностями функционального программирования в Java. Плохая новость — понять, как именно все устроено и как все работает, без тех же внутренних анонимных классов уже сложно.

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

Чтобы разобраться во всех нюансах функционального программирования в Java, нужны месяцы. Читать же такой код можно научиться за несколько часов. Поэтому предлагаем начать с малого. Да хоть с тех же потоков ввода-вывода.

Конкуренция и параллелизм

найдешь

  • Конкуренция — это способ одновременного решения множества задач
  • Параллелизм — это способ выполнения разных частей одной задачи

тут

  • Наличие нескольких потоков управления (например Thread в Java, корутина в Kotlin), если поток управления один, то конкурентного выполнения быть не может
  • Недетерминированный результат выполнения. Результат зависит от случайных событий, реализации и того как была проведена синхронизация. Даже если каждый поток полностью детерминированный, итоговый результат будет недетерминированным
  • Необязательно имеет несколько потоков управления
  • Может приводить к детерминированному результату, так например, результат умножения каждого элемента массива на число, не изменится, если умножать его по частям параллельно
  • битов (например в 32-разрядных машинах сложение происходит в одно действие, параллельно обрабатывая все 4 байта 32-разрядного числа)
  • инструкций (на одном ядре, в одном потоке процессор может выполнять инструкции параллельно, несмотря на то что код последовательный)
  • данных (существуют архитектуры с параллельной обработкой данных (Single Instruction Multiple Data), способные выполнять одну инструкцию на большом наборе данных)
  • задач (подразумевается наличие нескольких процессоров или ядер)

параллельного

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector