multithreading #multithread ing
multithreading
#multithread
ing
1
1: 2
2
Examples 2
2
2
3
3
4
Hello Multithreading - 7
? 8
2: 9
9
9
10
ThreadPools 10
Examples 11
ThreadPool 11
12
Runnables Callables 13
ThreadFactory 15
3: 17
17
17
17
Mutex 17
Examples 17
Mutex Java C ++ 17
21
ОколоYou can share this PDF with anyone you feel could benefit from it, downloaded the latest version from: multithreading
It is an unofficial and free multithreading ebook created for educational purposes. All the content is extracted from Stack Overflow Documentation, which is written by many hardworking individuals at Stack Overflow. It is neither affiliated with Stack Overflow nor official multithreading.
The content is released under Creative Commons BY-SA, and the list of contributors to each chapter are provided in the credits section at the end of this book. Images may be copyright of their respective owners unless otherwise specified. All trademarks and registered trademarks are the property of their respective company owners.
Use the content presented in this book at your own risk; it is not guaranteed to be correct nor accurate, please send your feedback and corrections to [email protected]
https://riptutorial.com/ru/home 1
глава 1: Начало работы с многопоточным
замечания
Многопоточность - это метод программирования, который состоит из деления задачи на отдельные потоки исполнения. Эти потоки выполняются одновременно, либо назначая разные процессорные ядра, либо путем временного среза.
При разработке многопоточной программы потоки должны быть сделаны независимо друг от друга, насколько это возможно, для достижения максимального ускорения. На практике потоки редко полностью независимы, что делает синхронизацию необходимой. Максимальное теоретическое ускорение можно рассчитать по закону Амдаля .
преимущества
Ускорьте время выполнения, эффективно используя доступные ресурсы обработки•Позволить процессу оставаться отзывчивым без необходимости разделения длительных вычислений или дорогостоящих операций ввода-вывода
•
Легко определять приоритеты определенных операций над другими•
Недостатки
Без тщательного проектирования могут быть введены труднодоступные ошибки•Создание потоков связано с некоторыми накладными расходами•
Examples
Цель
Темы - это части низкого уровня вычислительной системы, обработка команд. Он поддерживается / предоставляется аппаратным обеспечением CPU / MCU. Существуют также программные методы. Цель многопоточности делает расчеты параллельно друг другу, если это возможно. Таким образом, желаемый результат может быть получен в меньшем временном разрезе.
Тупики
Тупик возникает, когда каждый член какой-либо группы из двух или более потоков должен ждать, пока один из других членов что-то предпримет (например, чтобы освободить блокировку), прежде чем он сможет продолжить. Без вмешательства потоки будут ждать вечно.
https://riptutorial.com/ru/home 2
Псевдокод пример тупиковой конструкции:
thread_1 { acquire(A) ... acquire(B) ... release(A, B) } thread_2 { acquire(B) ... acquire(A) ... release(A, B) }
Тупик может произойти, когда thread_1 приобрел A , но еще не B , а thread_2 приобрел B , но не A Как показано на следующей диаграмме, оба потока будут ждать всегда.
Как избежать взаимоблокировокКак общее правило, минимизируйте использование блокировок и минимизируйте код между блокировкой и разблокировкой.
Приобретение замков в том же порядке
Реорганизация thread_2 решает проблему:
thread_2 { acquire(A) ... acquire(B) ... release(A, B) }
Оба потока получают ресурсы в том же порядке, что позволяет избежать взаимоблокировок.
Это решение известно как «Решение иерархии ресурсов». Он был предложен Дейкстре как решение проблемы «Обеденные философы».
Иногда, даже если вы указываете строгий порядок захвата блокировки, такой порядок сбора статических помех может быть выполнен динамически во время выполнения.
Рассмотрим следующий код:
https://riptutorial.com/ru/home 3
void doCriticalTask(Object A, Object B){ acquire(A){ acquire(B){ } } }
Здесь, даже если порядок сбора блокировки выглядит безопасным, он может вызвать тупик, когда thread_1 обращается к этому методу, например, Object_1 как параметр A и Object_2 как параметр B, а thread_2 выполняет в противоположном порядке, то есть Object_2, как параметр A и Object_1 в качестве параметра B.
В такой ситуации лучше иметь какое-то уникальное условие, полученное с использованием как Object_1, так и Object_2 с каким-то вычислением, например, с использованием hashcode
обоих объектов, поэтому всякий раз, когда в этот метод входит любой поток, в любом параметрическом порядке каждый раз, когда это уникальное условие выводит блокировка.
например, Say Object имеет уникальный ключ, например accountNumber в случае объекта Account.
void doCriticalTask(Object A, Object B){ int uniqueA = A.getAccntNumber(); int uniqueB = B.getAccntNumber(); if(uniqueA > uniqueB){ acquire(B){ acquire(A){ } } }else { acquire(A){ acquire(B){ } } } }
Условия гонки
Годом данных или состоянием гонки является проблема, которая может возникнуть, если многопоточная программа не синхронизирована должным образом. Если два или более потока обращаются к одной и той же памяти без синхронизации, и, по крайней мере, один из способов доступа является «записью», происходит гонка данных. Это приводит к зависящему от платформы, возможно, непоследовательному поведению программы. Например, результат вычисления может зависеть от планирования потоков.
Читатели-писатели Проблема :
writer_thread {
https://riptutorial.com/ru/home 4
write_to(buffer) } reader_thread { read_from(buffer) }
Простое решение:
writer_thread { lock(buffer) write_to(buffer) unlock(buffer) } reader_thread { lock(buffer) read_from(buffer) unlock(buffer) }
Это простое решение работает хорошо, если есть только один поток читателей, но если их больше одного, это замедляет выполнение без необходимости, потому что потоки чтения могут считываться одновременно.
Решение, которое позволяет избежать этой проблемы, может быть:
writer_thread { lock(reader_count) if(reader_count == 0) { write_to(buffer) } unlock(reader_count) } reader_thread { lock(reader_count) reader_count = reader_count + 1 unlock(reader_count) read_from(buffer) lock(reader_count) reader_count = reader_count - 1 unlock(reader_count) }
Обратите внимание, что reader_count заблокирован на протяжении всей операции записи, так что ни один читатель не может начать чтение, пока запись еще не закончена.
Теперь многие читатели могут читать одновременно, но может возникнуть новая проблема: reader_count может никогда не достигнуть 0 , так что нить писателя никогда не сможет записать в буфер. Это называется голодом , есть разные решения, чтобы избежать этого.
https://riptutorial.com/ru/home 5
Даже программы, которые могут показаться правильными, могут быть проблематичными:
boolean_variable = false writer_thread { boolean_variable = true } reader_thread { while_not(boolean_variable) { do_something() } }
Примерная программа никогда не может завершиться, поскольку нить читателя никогда не увидит обновление из потока писателя. Если, например, аппаратное обеспечение использует кэши процессора, значения могут быть кэшированы. И так как запись или чтение в нормальное поле не приводит к обновлению кеша, измененное значение никогда не будет видно нитью чтения.
C ++ и Java определяют в так называемой модели памяти, что правильно синхронизировано означает: модель памяти C ++, модель памяти Java .
В Java решение было бы объявить поле изменчивым:
volatile boolean boolean_field;
В C ++ решением было бы объявить поле как атомное:
std::atomic<bool> data_ready(false)
Гонка данных - это своего рода состояние гонки. Но не все условия гонки - это гонки данных. Следующие, вызванные более чем одним потоком, приводят к состоянию гонки, но не к гонке данных:
class Counter { private volatile int count = 0; public void addOne() { i++; } }
Он правильно синхронизирован в соответствии с спецификацией Java Memory Model,
поэтому это не гонка данных. Но все же это приводит к условиям гонки, например, результат зависит от чередования потоков.
Не все расы данных являются ошибками. Примером так называемого доброкачественного состояния гонки является sun.reflect.NativeMethodAccessorImpl:
https://riptutorial.com/ru/home 6
class NativeMethodAccessorImpl extends MethodAccessorImpl { private Method method; private DelegatingMethodAccessorImpl parent; private int numInvocations; NativeMethodAccessorImpl(Method method) { this.method = method; } public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException { if (++numInvocations > ReflectionFactory.inflationThreshold()) { MethodAccessorImpl acc = (MethodAccessorImpl) new MethodAccessorGenerator(). generateMethod(method.getDeclaringClass(), method.getName(), method.getParameterTypes(), method.getReturnType(), method.getExceptionTypes(), method.getModifiers()); parent.setDelegate(acc); } return invoke0(method, obj, args); } ... }
Здесь производительность кода важнее, чем правильность подсчета numInvocation.
Hello Multithreading - создание новых потоков
Этот простой пример показывает, как запустить несколько потоков в Java. Обратите внимание, что потоки не гарантируются для выполнения в порядке, и порядок выполнения может варьироваться для каждого прогона.
public class HelloMultithreading { public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread t = new Thread(new MyRunnable(i)); t.start(); } } public static class MyRunnable implements Runnable { private int mThreadId; public MyRunnable(int pThreadId) { super(); mThreadId = pThreadId; } @Override public void run() {
https://riptutorial.com/ru/home 7
System.out.println("Hello multithreading: thread " + mThreadId); } } }
Может ли тот же поток работать дважды?
Чаще всего вопрос заключался в том, что один и тот же поток можно запустить дважды.
Ответ для этого состоит в том, что один поток может запускаться только один раз.
если вы попытаетесь запустить тот же самый поток дважды, он будет выполняться в первый раз, но будет давать ошибку во второй раз, и ошибка будет IllegalThreadStateException.
пример :
public class TestThreadTwice1 extends Thread{ public void run(){ System.out.println("running..."); } public static void main(String args[]){ TestThreadTwice1 t1=new TestThreadTwice1(); t1.start(); t1.start(); } }
выход :
running Exception in thread "main" java.lang.IllegalThreadStateException
Прочитайте Начало работы с многопоточным онлайн: https://riptutorial.com/ru/multithreading/topic/1229/начало-работы-с-многопоточным
https://riptutorial.com/ru/home 8
глава 2: Исполнители
Синтаксис
ThreadPoolExecutor•
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
•
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
•
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
•
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
•
Executors.callable(PrivilegedAction<?> action)•
Executors.callable(PrivilegedExceptionAction<?> action)•
Executors.callable(Runnable task)•
Executors.callable(Runnable task, T result)•
Executors.defaultThreadFactory()•
Executors.newCachedThreadPool()•
Executors.newCachedThreadPool(ThreadFactory threadFactory)•
Executors.newFixedThreadPool(int nThreads)•
Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory)•
Executors.newScheduledThreadPool(int corePoolSize)•
Executors.newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)•
Executors.newSingleThreadExecutor()•
Executors.newSingleThreadExecutor(ThreadFactory threadFactory)•
параметры
параметр подробность
corePoolSize Минимальное количество потоков для хранения в пуле.
maximumPoolSize Максимальное количество потоков для пула.
https://riptutorial.com/ru/home 9
параметр подробность
KeepAliveTimeКогда количество потоков больше, чем ядро, потоки noncore (
избыточные потоки бездействия) будут ждать времени, определенного этим параметром для новых задач, до завершения.
единица измерения
Единица времени для keepAliveTime .
Тайм-аут максимальное время ожидания
workQueue Тип очереди, которую наш Исполнитель собирается использовать
threadFactoryЗавод, который будет использоваться при создании новых потоков
nThreads Количество потоков в пуле
исполнитель Основная реализация
задача задача запуска
результат Результат возврата
действие Привилегированное действие для запуска
подлежащий выкупу
Основная задача
замечания
Различные типы потоков и очереди, описанные ниже, были взяты из информации и знаний из блога [oracle documentation] [1] и [Jakob Jenkov] [2], где вы можете много узнать о параллелизме в Java.
Различные типы ThreadPools
SingleThreadExecutor: Executor, который использует один рабочий поток, работающий с неограниченной очередью, и использует предоставленный ThreadFactory для создания нового потока, когда это необходимо. В отличие от эквивалентного newFixedThreadPool (1,
threadFactory), возвращаемый исполнитель гарантированно не может быть перенастроен для использования дополнительных потоков.
FixedThreadPool: пул потоков, который повторно использует фиксированное количество потоков, работающих с общей неограниченной очередью, используя предоставленный
https://riptutorial.com/ru/home 10
ThreadFactory для создания новых потоков, когда это необходимо. В любой момент, в большинстве случаев nThreads будут активными задачами обработки. Если дополнительные задачи передаются, когда все потоки активны, они будут ждать в очереди до тех пор, пока поток не будет доступен. Если какой-либо поток завершается из-за сбоя во время выполнения перед завершением работы, новый, если потребуется, займет свое место для выполнения последующих задач. Нити в пуле будут существовать до тех пор, пока они не будут явно отключены.
CachedThreadPool: пул потоков, который при необходимости создает новые потоки, но будет использовать ранее созданные потоки, когда они будут доступны, и использует предоставленный ThreadFactory для создания новых потоков, когда это необходимо.
SingleThreadScheduledExecutor: однопоточный исполнитель, который может планировать выполнение команд по заданной задержке или выполнять их периодически. (Обратите внимание, что если этот единственный поток завершается из-за сбоя во время выполнения до выключения, новый, если потребуется, будет занят, чтобы выполнять последующие задачи.) Гарантируется выполнение задач последовательно и не более одной задачи будет активна в любой момент времени. В отличие от иначе эквивалентного newScheduledThreadPool (1, threadFactory), возвращаемый исполнитель гарантированно не может быть перенастроен для использования дополнительных потоков.
ScheduledThreadPool: пул потоков, который может планировать выполнение команд после заданной задержки или выполнять их периодически. Различные типы рабочих очередей
Examples
Определение новой ThreadPool
ThreadPool - это ExecutorService который выполняет каждую отправленную задачу, используя один из, возможно, нескольких объединенных потоков, обычно настраиваемых с использованием фабричных методов Executors.
Вот базовый код для инициализации нового ThreadPool в качестве одноэлементного приложения для вашего приложения:
public final class ThreadPool { private static final String TAG = "ThreadPool"; private static final int CORE_POOL_SIZE = 4; private static final int MAX_POOL_SIZE = 8; private static final int KEEP_ALIVE_TIME = 10; // 10 seconds private final Executor mExecutor; private static ThreadPool sThreadPoolInstance; private ThreadPool() {
https://riptutorial.com/ru/home 11
mExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); } public void execute(Runnable runnable) { mExecutor.execute(runnable); } public synchronized static ThreadPool getThreadPoolInstance() { if (sThreadPoolInstance == null) { Log.i(TAG, "[getThreadManagerInstance] New Instance"); sThreadPoolInstance = new ThreadPool(); } return sThreadPoolInstance; } }
У вас есть два способа вызова метода runnable, используйте execute() или submit() . разница между ними заключается в том, что submit() возвращает объект Future который позволяет вам программно отменить Callable поток, когда объект T возвращается из обратного вызова Callable . Вы можете больше узнать о Future здесь
Будущие и вызывающие
Одной из функций, которые мы можем использовать с Threadpool, является метод submit() который позволяет нам знать, когда поток завершает работу. Мы можем сделать это благодаря объекту Future , который возвращает нам объект из Callable, который мы можем использовать для наших собственных задач.
Ниже приведен пример использования экземпляра Callable:
public class CallablesExample{ //Create MyCustomCallable instance List<Future<String>> mFutureList = new ArrayList<Future<String>>(); //Create a list to save the Futures from the Callable Callable<String> mCallable = new MyCustomCallable(); public void main(String args[]){ //Get ExecutorService from Executors utility class, Creating a 5 threads pool. ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { //submit Callable tasks to be executed by thread pool Future<String> future = executor.submit(mCallable); //add Future to the list, we can get return value using Future mFutureList.add(future); } for (Future<String> fut : mFutureList) { try { //Print the return value of Future, Notice the output delay in console
https://riptutorial.com/ru/home 12
//because Future.get() stop the thread till the task have been completed System.out.println(new Date() + "::" + fut.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } //Shut down the service executor.shutdown(); } class MyCustomCallable implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); //return the thread name executing this callable task return Thread.currentThread().getName(); } } }
Как вы можете видеть, мы создаем Threadpool с 5 потоками, это означает, что мы можем бросить 5 callables параллельно. Когда потоки закончатся, мы получим и объект Future из вызываемого, в данном случае имени потока.
ПРЕДУПРЕЖДЕНИЕ
В этом примере мы просто используем фьючерсы как объект внутри массива, чтобы знать, сколько потоков мы выполняем, и печатать много раз консоль журнала с теми данными, которые мы хотим. Но если мы хотим использовать метод Future.get() , чтобы вернуть нам данные, которые мы сохранили ранее в вызываемом, мы заблокируем поток до завершения задачи. Будьте осторожны с такими звонками, когда вы хотите выполнить это как можно быстрее
Пользовательские Runnables вместо Callables
Еще одна хорошая практика для проверки завершения наших потоков без блокирования потока, ожидающего восстановления объекта Future из нашего Callable, - это создать нашу собственную реализацию для Runnables, используя ее вместе с методом execute() .
В следующем примере я показываю пользовательский класс, который реализует Runnable
с внутренним обратным вызовом, с помощью которого мы можем знать, когда исполняемые файлы закончены, и использовать их позже в нашем ThreadPool:
public class CallbackTask implements Runnable { private final Runnable mTask; private final RunnableCallback mCallback; public CallbackTask(Runnable task, RunnableCallback runnableCallback) { this.mTask = task; this.mCallback = runnableCallback; }
https://riptutorial.com/ru/home 13
public void run() { long startRunnable = System.currentTimeMillis(); mTask.run(); mCallback.onRunnableComplete(startRunnable); } public interface RunnableCallback { void onRunnableComplete(long runnableStartTime); } }
И вот наша реализация ThreadExecutor:
public class ThreadExecutorExample implements ThreadExecutor { private static String TAG = "ThreadExecutorExample"; public static final int THREADPOOL_SIZE = 4; private long mSubmittedTasks; private long mCompletedTasks; private long mNotCompletedTasks; private ThreadPoolExecutor mThreadPoolExecutor; public ThreadExecutorExample() { Log.i(TAG, "[ThreadExecutorImpl] Initializing ThreadExecutorImpl"); Log.i(TAG, "[ThreadExecutorImpl] current cores: " + Runtime.getRuntime().availableProcessors()); this.mThreadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREADPOOL_SIZE); } @Override public void execute(Runnable runnable) { try { if (runnable == null) { Log.e(TAG, "[execute] Runnable to execute cannot be null"); return; } Log.i(TAG, "[execute] Executing new Thread"); this.mThreadPoolExecutor.execute(new CallbackTask(runnable, new CallbackTask.RunnableCallback() { @Override public void onRunnableComplete(long RunnableStartTime) { mSubmittedTasks = mThreadPoolExecutor.getTaskCount(); mCompletedTasks = mThreadPoolExecutor.getCompletedTaskCount(); mNotCompletedTasks = mSubmittedTasks - mCompletedTasks; // approximate Log.i(TAG, "[execute] [onRunnableComplete] Runnable complete in " + (System.currentTimeMillis() - RunnableStartTime) + "ms"); Log.i(TAG, "[execute] [onRunnableComplete] Current threads working " + mNotCompletedTasks); } })); } catch (Exception e) { e.printStackTrace();
https://riptutorial.com/ru/home 14
Log.e(TAG, "[execute] Error, shutDown the Executor"); this.mThreadPoolExecutor.shutdown(); } } } /** * Executor thread abstraction created to change the execution context from any thread from out ThreadExecutor. */ interface ThreadExecutor extends Executor { void execute(Runnable runnable); }
Я сделал этот пример, чтобы проверить скорость моих потоков в миллисекундах, когда они выполнены, без использования Future. Вы можете взять этот пример и добавить его в свое приложение, чтобы контролировать работу параллельной задачи и завершенные / завершенные. Проверка всего момента, время, необходимое для выполнения этих потоков.
Добавление ThreadFactory к Исполнителю
Мы используем ExecutorService для назначения потоков из внутреннего пула потоков или их создания по требованию для выполнения задач. Каждый ExecutorService имеет ThreadFactory, но ExecutorService будет использовать всегда по умолчанию, если мы не настроим его. Почему мы должны это делать?
Чтобы задать более описательное имя потока. По умолчанию ThreadFactory
предоставляет имена потоков в виде пула-m-thread-n, такие как pool-1-thread-1, pool-
2-thread-1, pool-3-thread-1 и т. Д. Если вы пытаетесь отлаживать или контролировать что-то, трудно понять, что это за темы
•
Задайте настраиваемый статус Daemon, по умолчанию ThreadFactory создает результаты без демона.
•
Задайте приоритет для наших потоков, по умолчанию ThreadFactory установил средний приоритет для всех своих потоков.
•
Вы можете указать UncaughtExceptionHandler для нашего потока, используя setUncaughtExceptionHandler() для объекта thread. Это получает обратный вызов, когда метод запуска Thread's генерирует неперехваченное исключение.
•
Вот простая реализация ThreadFactory поверх ThreadPool.
public class ThreadExecutorExample implements ThreadExecutor { private static String TAG = "ThreadExecutorExample"; private static final int INITIAL_POOL_SIZE = 3; private static final int MAX_POOL_SIZE = 5;
https://riptutorial.com/ru/home 15
// Sets the amount of time an idle thread waits before terminating private static final int KEEP_ALIVE_TIME = 10; // Sets the Time Unit to seconds private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; private final BlockingQueue<Runnable> workQueue; private final ThreadPoolExecutor threadPoolExecutor; private final ThreadFactory threadFactory; private ThreadPoolExecutor mThreadPoolExecutor; public ThreadExecutorExample() { this.workQueue = new LinkedBlockingQueue<>(); this.threadFactory = new CustomThreadFactory(); this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory); } public void execute(Runnable runnable) { if (runnable == null) { return; } this.threadPoolExecutor.execute(runnable); } private static class CustomThreadFactory implements ThreadFactory { private static final String THREAD_NAME = "thread_"; private int counter = 0; @Override public Thread newThread(Runnable runnable) { return new Thread(runnable, THREAD_NAME + counter++); } } } /** * Executor thread abstraction created to change the execution context from any thread from out ThreadExecutor. */ interface ThreadExecutor extends Executor { void execute(Runnable runnable); }
В этом примере просто измените имя потока с помощью счетчика, но мы можем его изменить до тех пор, пока мы хотим.
Прочитайте Исполнители онлайн: https://riptutorial.com/ru/multithreading/topic/6710/
исполнители
https://riptutorial.com/ru/home 16
глава 3: Семафоры и мьютексы
Вступление
Семафоры и мьютексы - это средства параллелизма, используемые для синхронизации доступа нескольких потоков к общим ресурсам.
замечания
семафорВот блестящее объяснение этого вопроса Stackoverflow :
Подумайте о семафорах как вышибалы в ночном клубе. Есть определенное количество людей, которые разрешены в клубе сразу. Если клуб заполнен, никто не может войти, но как только один человек покинет другого человека, он может войти.
Это просто способ ограничить количество потребителей определенным ресурсом. Например, чтобы ограничить количество одновременных вызовов в базе данных в приложении.
Mutex
Мьютекс - это семафор из 1 (т. Е. Только один поток за раз). Используя метафору ночного клуба, подумайте о мьютексе с точки зрения ванной комнаты в ночном клубе. Одновременно допускался только один человек.
Examples
Mutex в Java и C ++
Хотя Java не имеет класса Mutex, вы можете имитировать Mutex с использованием семафора из 1. Следующий пример выполняет два потока с блокировкой и без них. Без блокировки программа выплевывает несколько случайный порядок выходных символов ($ или #). С блокировкой программа выплескивает симпатичные упорядоченные наборы символов либо #####, либо $$$$$, но никогда не смешивает # и $.
import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadLocalRandom;
https://riptutorial.com/ru/home 17
public class MutexTest { static Semaphore semaphore = new Semaphore(1); static class MyThread extends Thread { boolean lock; char c = ' '; MyThread(boolean lock, char c) { this.lock = lock; this.c = c; } public void run() { try { // Generate a random number between 0 & 50 // The random nbr is used to simulate the "unplanned" // execution of the concurrent code int randomNbr = ThreadLocalRandom.current().nextInt(0, 50 + 1); for (int j=0; j<10; ++j) { if(lock) semaphore.acquire(); try { for (int i=0; i<5; ++i) { System.out.print(c); Thread.sleep(randomNbr); } } finally { if(lock) semaphore.release(); } System.out.print('|'); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { System.out.println("Without Locking:"); MyThread th1 = new MyThread(false, '$'); th1.start(); MyThread th2 = new MyThread(false, '#'); th2.start(); th1.join(); th2.join(); System.out.println('\n'); System.out.println("With Locking:"); MyThread th3 = new MyThread(true, '$'); th3.start(); MyThread th4 = new MyThread(true, '#'); th4.start(); th3.join(); th4.join(); System.out.println('\n'); }
https://riptutorial.com/ru/home 18
}
Запустить javac MutexTest.java; java MutexTest , и вы получите что-то вроде этого:
Без блокировки: # $$$$$ | $$$$$ | $$ # $$$ | $$$$$ | $$$$ # $ | $$$$$ | $$$$$ | $ # $$$$ | $$$$$ | $$$ # $$ || ##### | ##### | ##### | ##### | ##### | # #### | ##### | ##### | ##### |
С блокировкой: $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $ $$$$ | ##### | $$$$$ | ##### |
Вот такой же пример в C ++:
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex #include <random> // std::random_device class MutextTest { private: static std::mutex mtx; // mutex for critical section public: static void run(bool lock, char c) { // Generate a random number between 0 & 50 // The random nbr is used to simulate the "unplanned" // execution of the concurrent code std::uniform_int_distribution<int> dist(0, 50); std::random_device rd; int randomNbr = dist(rd); //std::cout << randomNbr << '\n'; for(int j=0; j<10; ++j) { if(lock) mtx.lock(); for (int i=0; i<5; ++i) { std::cout << c << std::flush; std::this_thread::sleep_for(std::chrono::milliseconds(randomNbr)); } std::cout << '|'; if(lock) mtx.unlock(); } } }; std::mutex MutextTest::mtx; int main() { std::cout << "Without Locking:\n"; std::thread th1 (MutextTest::run, false, '$'); std::thread th2 (MutextTest::run, false, '#'); th1.join(); th2.join(); std::cout << "\n\n";
https://riptutorial.com/ru/home 19
std::cout << "With Locking:\n"; std::thread th3 (MutextTest::run, true, '$'); std::thread th4 (MutextTest::run, true, '#'); th3.join(); th4.join(); std::cout << '\n'; return 0; }
Запустить g++ --std=c++11 MutexTest.cpp; ./a.out , и вы получите что-то вроде этого:
Без блокировки: $ # $ # $ # $ # $ # | $ | # $ # $ # $ # $ # | $$ | # $ # $ # $ # | $ # $ | # $ # $ # $ # | $ $ # $ | # $ # $ # | $ # $ # $ | # $ # $ # | $ # $$ # $ | # $ # | $ # $ # $ # $ | # $ # | $ # $ # $ # $$ | # | $ # $ # $ # $ # $ | # | #### |
С блокировкой: $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $$$$$ | ##### | $ $$$$ | ##### | $$$$$ | ##### |
Прочитайте Семафоры и мьютексы онлайн: https://riptutorial.com/ru/multithreading/topic/10861/семафоры-и-мьютексы
https://riptutorial.com/ru/home 20
кредиты
S. No
Главы Contributors
1Начало работы с многопоточным
alain, Amit Gujarathi, Boo Radley, Community, Gul Md Ershad, james large, Jim, John Odom, Mert Gülsoy, RamenChef, Thomas Krieger, vvtx, Zim-Zam O'Pootertoot
2 Исполнители Francisco Durdin Garcia, Guillermo Orellana Ruiz, vvtx
3Семафоры и мьютексы
John DiFini
https://riptutorial.com/ru/home 21