Отношения любви и ненависти между операционными системами и параллелизмом

Java задняя часть

Причина, по которой я не спешил писать параллелизм, заключается в том, что я не могу понять操作系统, Теперь я снова почистил операционную систему, на этот раз я пытаюсь написать параллелизм, чтобы увидеть, смогу ли я написать это ясно, скромный редактор онлайн для поддержки ... Я планирую объединить операционную систему и параллелизм одновременно время Путь.

История параллелизма

На заре компьютеров не было операционной системы, и для выполнения программы требовался только один процесс, который должен был выполняться последовательно от начала до конца. Любой ресурс будет обслуживать эту программу, она должна существовать浪费资源Случай.

Упомянутая здесь трата ресурсов относится к ситуации, когда ресурсы простаивают и используются не полностью.

Операционная система привносит в наши программы并发性, операционная система заставляет нашу программу запускать несколько программ одновременно, программа — это процесс, что эквивалентно одновременному запуску нескольких процессов.

операционная система представляет собой并发系统, Параллелизм является очень важной функцией операционной системы.Операционная система имеет возможность обрабатывать и планировать несколько программ одновременно.Например, несколько устройств ввода/вывода вводятся и выводятся одновременно;устройство ввода/вывода и вычисления ЦП выполняются одновременно; несколько системных и пользовательских программ активируются для выполнения поочередно и вперемежку. В то время как операционная система координирует и распределяет процессы, операционная система также выделяет разные ресурсы для разных процессов.

Операционная система реализует одновременную работу нескольких программ, что решает проблему, которую не может решить одна программа.Есть три основных момента:

  • 资源利用率, как мы упоминали выше, в одном процессе происходит пустая трата ресурсов.Например, когда вы предоставляете разрешения для папки, программа ввода не может принимать внешние вводимые символы и может принимать внешние символы только после предоставления разрешений. . В общем, выполнять другую работу во время ожидания программы невозможно. Если другая программа может работать во время ожидания программы, это значительно улучшит использование ресурсов. (Ресурсы не устают) Потому что он не умеет грести~
  • 公平性, разные пользователи и программы имеют одинаковые права на использование ресурсов на компьютере. Эффективным способом запуска является разделение временных отрезков для использования ресурсов различными программами, но следует отметить, что операционная система может определять приоритет различных процессов, хотя каждый процесс имеет право справедливо пользоваться ресурсами, но перед каждым процессом. процесс освобождает ресурсы, процесс с более высоким приоритетом захватывает ресурсы, что приводит к тому, что процесс с более низким приоритетом не может получить ресурсы, что со временем приведет к голоданию процесса.
  • 便利性, один процесс не может общаться. Я думаю, что общение на самом деле является своего рода避雷针стратегии, суть коммуникации заключается в信息交换, своевременный обмен информацией может избежать信息孤岛, выполнять повторяющуюся работу; все, что можно делать одновременно, можно добиться и последовательного программирования, но этот метод очень неэффективен, это своего рода阻塞式из.

Однако последовательное программирование (также известное как串行编程) ни一无是处, преимущество последовательного программирования заключается в егоИнтуитивность и простота, Объективно говоря, последовательное программирование больше подходит для образа мышления нашего человеческого мозга, но последовательное программирование нас не устраивает,we want it more!!!. Использование ресурсов, справедливость и удобство движут процессами, а также线程внешность.

Если вы не совсем понимаете разницу между процессами и потоками, то я вам объясню, исходя из своего многолетнего опыта работы с операционными системами (хвастовство, а на самом деле пол года):Процесс — это приложение, а поток — это последовательный поток в приложении..

или阮一峰Учитель также дал вам простое для понимания объяснение

Взято изВууху. Руань Ифэн.com/blog/2013/0…

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

нить описывается как轻量级Облегченность процесса выражается в том, что создание и уничтожение потоков намного меньше, чем накладные расходы процесса.

Примечание. Любые сравнения являются относительными.

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

нить

Преимущества и недостатки

Разумное использование потоков — это искусство, а разумное написание точной многопоточной программы — это искусство.Если потоки используются правильно, затраты на разработку и обслуживание программы могут быть эффективно снижены.

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

Java хорошо реализует набор средств разработки в пользовательском пространстве и предоставляет системные вызовы в пространстве ядра для поддержки многопоточного программирования.Java поддерживает богатые библиотеки классов.java.util.concurrentи кроссплатформенный内存模型В то же время это также повышает порог для разработчиков.Параллелизм всегда был темой высокого уровня, но теперь он также стал необходимым качеством для основных разработчиков.

Хотя у потоков много преимуществ, очень сложно писать правильные многопоточные (параллельные) программы.Ошибки в параллельных программах часто появляются и исчезают странным образом.Когда вы думаете, что проблемы нет, она появляется,难以定位Это особенность параллельных программ, поэтому вам необходимо иметь прочные базовые навыки параллелизма на этой основе. Итак, почему возникает параллелизм?

почему параллелизм

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

Внутри ЦП находится структура регистров, и скорость доступа к регистрам выше, чем у高速缓存, скорость доступа к кешу выше, чем к памяти, а самая медленная — к диску.

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

Поскольку скорость ЦП слишком высока, чтобы воспользоваться скоростью ЦП и сбалансировать разницу в скорости между тремя, компьютерная система, операционная система и компилятор внесли свой вклад, в основном следующим образом:

  • ЦП использует кеш для нейтрализации различий в скорости доступа к памяти.
  • Операционная система обеспечивает планирование процессов и потоков, позволяя ЦП разделять потоки во времени при выполнении инструкций, позволяя памяти и диску непрерывно взаимодействовать.CPU 时间片Способность выполнять разные задачи, тем самым уравновешивая различия между тремя
  • Компилятор обеспечивает оптимизированный порядок выполнения инструкций, чтобы кеш можно было использовать разумно.

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

Проблемы безопасности с потоками

Безопасность потоков очень сложна, и без принятия同步机制В случае нескольких потоков операции выполнения в нескольких потоках часто непредсказуемы, что также является одной из проблем, связанных с несколькими потоками.Давайте приведем фрагмент кода, чтобы увидеть, в чем заключаются проблемы безопасности.

public class TSynchronized implements Runnable{

    static int i = 0;

    public void increase(){
        i++;
    }


    @Override
    public void run() {
        for(int i = 0;i < 1000;i++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        TSynchronized tSynchronized = new TSynchronized();
        Thread aThread = new Thread(tSynchronized);
        Thread bThread = new Thread(tSynchronized);
        aThread.start();
        bThread.start();
        System.out.println("i = " + i);
    }
}

После вывода этой программы вы обнаружите, что значение i каждый раз разное, что не соответствует нашему прогнозу, так почему же это происходит? Давайте сначала проанализируем запущенный процесс программы.

TSynchronizedРеализует интерфейс Runnable и определяет статическую переменнуюi, затем вincreaseЗначение I добавляется каждый раз в методе, и вызов цикла выполняется в его методе RUN, и всего выполняется 1000 раз.

проблемы с видимостью

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

Если это представить картинкой, я думаю, что это будет следующим образом

В многоядерную эпоху, из-за существования нескольких ядер, каждое ядро ​​​​может запускать поток независимо, и каждый ЦП имеет свой собственный кеш.В настоящее время не так просто решить согласованность данных между кешем ЦП и памятью. Когда потоки выполняются на разных ЦП, эти потоки работают с разными кэшами ЦП.

Поскольку i является статической переменной и не защищена никакими мерами безопасности потоков, несколько потоков будут одновременно изменять значение i, поэтому мы думаем, что i не является потокобезопасным, и этот результат вызван чтением i в aThread и bThread. значения не видны друг другу, так что это связано с可见性привести к проблемам безопасности потоков.

#### проблема атомарности

Программа, которая выглядит очень обычной, но из-за двух потоковaThreadа такжеbThreadПопеременное выполнение давало разные результаты. Но первопричина не вызвана созданием двух потоков, многопоточность является лишь необходимым условием безопасности потоков, а окончательная первопричина проявляется вi++эта операция.

Что случилось с этой операцией? Разве это не просто операция, которая увеличивает i ? то естьi++ => i = i + 1, как это может быть проблемой?

потому чтоi++Не один原子性Операция, подумайте об этом, i++ на самом деле состоит из трех шагов: прочитать значение i, выполнить операцию i + 1, а затем переназначить значение, полученное i + 1, i (записать результат в память).

Когда два потока начинают работать, каждый поток будет считывать значение i в кэш ЦП, затем выполнять операцию + 1, а затем записывать значение после + 1 в память. Поскольку каждый поток имеет свой стек виртуальной машины и счетчик программ, между ними нет обмена данными, поэтому при выполнении aThread операции +1 данные будут записываться в память, а после выполнения bThread операции +1 он также будет писать Данные записываются в память, поскольку цикл выполнения кванта времени ЦП неясен, поэтому, когда aThread не записал данные в память, bThread прочитает данные в памяти, затем выполнит операцию +1 и затем запишите обратно в память, тем самым перезаписав значение i, в результате чего усилия aThread будут напрасными.

Почему возникает проблема с вышеуказанным переключением потоков?

Давайте сначала рассмотрим порядок выполнения двух потоков при нормальных обстоятельствах (то есть без проблем с безопасностью потоков).

Видно, что когда aThread выполняет полную операцию i++, операционная система переключает поток с aThread -> bThread, что является наиболее идеальной операцией.读取/增加/写入На этапе происходит переключение потоков, что вызовет проблемы с безопасностью потоков. Например, как показано ниже

В начале i = 0 в памяти, aThread считывает значение в памяти и считывает его в свой регистр, и выполняет операцию +1.В это время происходит переключение потока, и bThread начинает выполнять и читать значение в память и прочитать его в свой регистр, в это время происходит переключение потока, поток переключается на aThread для запуска, aThread записывает значение своего регистра обратно в память, и в это время снова происходит переключение потока, из aThread -> bThread поток bThread прибавляет 1 к значению собственного регистра и потом записывает обратно в память.После записи значение в памяти не 2, а 1, и значение i в памяти затирается .

мы упоминали выше原子性Эта концепция, то что такое атомарность?

Атомарная операция параллельного программирования — это операция, которая выполняется полностью независимо от любого другого процесса.Атомарные операции в основном используются в современных операционных системах и системах параллельной обработки.

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

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

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

проблема с заказом

В параллельном программировании это также приносит много головной боли.有序性Проблема упорядоченности, как следует из названия, заключается в последовательности, которая относится к порядку выполнения инструкций в компьютере. Очень очевидный пример в JVM类加载

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

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

проблема активности

Многопоточность также приносит活跃性Вопрос, как вы определяете проблему деятельности? Вопросы деятельности сосредоточены напроизойдет ли что-то.

Если каждый поток в группе потоков ожидает события, которое может быть инициировано только другим потоком в группе, эта ситуация может привести к взаимоблокировке..

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

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

Если тупик очень痴情, тогда活锁выразить в предложении弄巧成拙.

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

Теперь представьте пару параллельных потоков, использующих два ресурса. После того, как их соответствующие попытки получить другую блокировку потерпят неудачу, оба потока снимут блокировки, которые они удерживают, и попытаются снова, и этот процесс будет продолжать повторяться. Очевидно, что в этом процессе нет блокировки потока, но поток все равно не выполняется вниз, такая ситуация называется活锁(livelock).

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

while(true){...}

for(;;){}

В многопоточности, например, aThread и bThread нуждаются в определенных ресурсах, aThread продолжает занимать ресурсы и не освобождает их, а bThread не может выполняться все время, что вызовет проблемы с активностью, а потоки bThread будут генерировать饥饿, о чем мы поговорим позже.

проблемы с производительностью

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

В многопоточности есть очень важный фактор производительности, о котором мы упоминали выше.线程切换, также известен как上下文切换(Context Switch), эта операция дорогая.

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

При переключении контекста контекст сохраняется и восстанавливается, локальность теряется, и много времени тратится на переключение потоков вместо их выполнения.

Почему переключение потоков такое дорогое? Переключение между потоками включает следующие шаги

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

Несколько способов вызвать переключение потоков

Переключение между потоками обычно является проблемой, которую необходимо рассматривать на уровне операционной системы, так как же можно вызвать переключение контекста потока? Или каковы стимулы для переключения потоков? Существуют в основном следующие способы вызвать переключение контекста

  • Текущая выполняемая задача завершена, и ЦП системы обычно планирует выполнение следующего потока.
  • Текущая выполняемая задача сталкивается с блокирующими операциями, такими как ввод-вывод, и планировщик потоков приостанавливает эту задачу и продолжает планировать следующую задачу.
  • Несколько задач одновременно вытесняют ресурсы блокировки Текущая задача не получает ресурсы блокировки, приостанавливается планировщиком потоков и продолжает планировать следующую задачу.
  • Код пользователя приостанавливает текущую задачу, например, поток, выполняющий метод сна, освобождая ЦП.
  • Используйте аппаратные прерывания для переключения контекста

потокобезопасность

В Java для достижения потокобезопасности мы должны правильно использовать потоки и блокировки, но это только один из способов обеспечить потокобезопасность Чтобы написать правильный потокобезопасный код, ядром является управление операциями доступа к состоянию. Самое важное это самое共享(Shared)и可变(Mutable)статус. Только общие и изменяемые переменные будут иметь проблемы, частные переменные не будут иметь проблем, см.程序计数器.

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

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

  • Не разделяйте переменные между несколькими потоками
  • Сделать общие переменные неизменяемыми

Мы много раз говорили о безопасности потоков, так что же такое безопасность потоков?

Что такое потокобезопасность

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

Один поток — это многопоток с числом потоков, равным 1, и один поток должен быть потокобезопасным. Чтение значения переменной не представляет проблемы безопасности, поскольку значение переменной не будет изменено, сколько бы раз оно ни читалось.

атомарность

Мы упомянули концепцию атомарности выше, вы можете думать об атомарной операции как о不可分割В целом у него всего два результата, либо все выполняются, либо все откатываются. Вы можете думать об атомарности как婚姻关系Один из них, мужчины и женщины, дают только два результата,好好的а также说散就散, жизнь среднего человека можно рассматривать как некую атомарность, конечно не исключаем时间管理(线程切换)Например, мы знаем, что переключение потоков обязательно будет сопровождаться проблемами с безопасностью. Люди, желающие выйти на улицу, также вызовут два результата. Эти два результата соответствуют двум результатам безопасности: безопасность потоков (хорошо) и небезопасность потоков (скажем, это уйдет).

состояние гонки

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

public class RaceCondition {
  
  private Signleton single = null;
  public Signleton newSingleton(){
    if(single == null){
      single = new Signleton();
    }
    return single;
  }
  
}

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

запорный механизм

В Java существует множество способов блокировки и защиты общих и изменяемых ресурсов. Java предоставляет встроенный механизм защиты ресурсов:synchronizedключевое слово, имеющее три механизма защиты

  • Заблокируйте метод, чтобы гарантировать, что только один из нескольких потоков выполняет метод;
  • Заблокируйте экземпляр объекта (в нашем обсуждении выше переменные можно заменить объектами), чтобы гарантировать, что только один поток из множества потоков может получить доступ к экземпляру объекта;
  • Заблокируйте объект класса, чтобы гарантировать, что только один поток может получить доступ к ресурсам в классе для нескольких потоков.

Блок кода, в котором ключевое слово synchronized защищает ресурсы, широко известен как同步代码块(Synchronized Block),Например

synchronized(lock){
  // 线程安全的代码
}

Каждый объект Java можно использовать как блокировку, реализующую синхронизацию, эти блокировки называются内置锁(Instrinsic Lock)или监视器锁(Monitor Lock). Поток автоматически получает блокировку перед входом в код синхронизации и автоматически снимает блокировку при выходе из кода синхронизации, и независимо от того, выходит ли он через обычный путь выполнения или путь исключения, единственный способ получить встроенную блокировку — это введите этот защищенный блокировкой блок кода синхронизации или метод.

Другая неявная семантика синхронизированного заключается в том, что互斥, взаимное исключение означает独占, блокировку удерживает не более одного потока. Когда поток A пытается получить блокировку, удерживаемую потоком B, поток A должен ждать или блокироваться до тех пор, пока поток B не снимет блокировку. Если поток B не снимает блокировку, поток A будет продолжать ждать .

Когда поток A получает блокировку, удерживаемую потоком B, поток A должен ожидать или блокироваться, но поток B, который получает блокировку, может повторно входить, и смысл повторного входа может быть представлен фрагментом кода.

public class Retreent {
  
  public synchronized void doSomething(){
    doSomethingElse();
    System.out.println("doSomething......");
  }
  
  public synchronized void doSomethingElse(){
    System.out.println("doSomethingElse......");
}

Поток, который получает блокировку метода doSomething(), может выполнить метод doSomethingElse() и может повторно выполнить содержимое метода doSomething() после выполнения. Блокировка повторного входа также поддерживает повторный вход между подклассами и родительскими классами, о чем мы расскажем позже.

volatileлегкийsynchronized, то есть облегченный метод блокировки, volatile блокирует объекты со стороны, обеспечивая видимость общих переменных. Видимость означает, что когда один поток изменяет общую переменную, другой поток может看见Это измененное значение. volatile дороже в исполнении, чемsynchronizedГораздо ниже, потому что volatile не вызывает переключения контекста потока.

Что касается конкретной реализации volatile, мы поговорим об этом позже.

мы также можем использовать原子类Для обеспечения безопасности потоков атомарный класс фактическиrt.jarНиже сatomicкласс, начиная с

Кроме того, мы также можем использоватьjava.util.concurrentПоточно-безопасный класс коллекции в наборе инструментов обеспечивает потокобезопасность Конкретный класс реализации и его принцип будут обсуждаться позже.

Привет всем, меня зовут cxuan. Я написал четыре PDF-файла самостоятельно, а именно: основные сведения о Java, основные сведения о HTTP, основные сведения о компьютере и основные сведения об операционной системе. Информация о качестве.