Многопоточность в Java

Здравствуйте! В этой статье я вкратце расскажу вам о процессах, потоках, и об основах многопоточного программирования на языке Java.
Наиболее очевидная область применения многопоточности – это программирование интерфейсов. Многопоточность незаменима тогда, когда необходимо, чтобы графический интерфейс продолжал отзываться на действия пользователя во время выполнения некоторой обработки информации. Например, поток, отвечающий за интерфейс, может ждать завершения другого потока, загружающего файл из интернета, и в это время выводить некоторую анимацию или обновлять прогресс-бар. Кроме того он может остановить поток загружающий файл, если была нажата кнопка «отмена».

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

Давайте начнем. Сначала о процессах.

Процессы

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

Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.

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

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

Потоки

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

Следует отдельно обговорить фразу «параллельно с другими потоками». Известно, что на одно ядро процессора, в каждый момент времени, приходится одна единица исполнения. То есть одноядерный процессор может обрабатывать команды только последовательно, по одной за раз (в упрощенном случае). Однако запуск нескольких параллельных потоков возможен и в системах с одноядерными процессорами. В этом случае система будет периодически переключаться между потоками, поочередно давая выполняться то одному, то другому потоку. Такая схема называется псевдо-параллелизмом. Система запоминает состояние (контекст) каждого потока, перед тем как переключиться на другой поток, и восстанавливает его по возвращению к выполнению потока. В контекст потока входят такие параметры, как стек, набор значений регистров процессора, адрес исполняемой команды и прочее…

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

Вот как это выглядит:

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

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

Запуск потоков

Каждый процесс имеет хотя бы один выполняющийся поток. Тот поток, с которого начинается выполнение программы, называется главным. В языке Java, после создания процесса, выполнение главного потока начинается с метода main(). Затем, по мере необходимости, в заданных программистом местах, и при выполнении заданных им же условий, запускаются другие, побочные потоки.

В языке Java поток представляется в виде объекта-потомка класса Thread. Этот класс инкапсулирует стандартные механизмы работы с потоком.

Запустить новый поток можно двумя способами:

Способ 1

Создать объект класса Thread, передав ему в конструкторе нечто, реализующее интерфейс Runnable. Этот интерфейс содержит метод run(), который будет выполняться в новом потоке. Поток закончит выполнение, когда завершится его метод run().

Выглядит это так:

class SomeThing			//Нечто, реализующее интерфейс Runnable implements Runnable		//(содержащее метод run()) { 	public void run()		//Этот метод будет выполняться в побочном потоке 	{ 		System.out.println("Привет из побочного потока!"); 	} }  public class Program			//Класс с методом main() { 	static SomeThing mThing;	//mThing - объект класса, реализующего интерфейс Runnable 	 	public static void main(String[] args) 	{ 		mThing = new SomeThing();				  		Thread myThready = new Thread(mThing);	//Создание потока "myThready" 		myThready.start();				//Запуск потока  		System.out.println("Главный поток завершён..."); 	} } 

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

public class Program		//Класс с методом main(). { 	public static void main(String[] args) 	{ 		//Создание потока 		Thread myThready = new Thread(new Runnable() 		{ 			public void run() //Этот метод будет выполняться в побочном потоке 			{ 				System.out.println("Привет из побочного потока!"); 			} 		}); 		myThready.start();	//Запуск потока  		System.out.println("Главный поток завершён..."); 	} } 
Способ 2

Создать потомка класса Thread и переопределить его метод run():

class AffableThread extends Thread { 	@Override 	public void run()	//Этот метод будет выполнен в побочном потоке 	{ 		System.out.println("Привет из побочного потока!"); 	} }  public class Program { 	static AffableThread mSecondThread; 	 	public static void main(String[] args) 	{ 		mSecondThread = new AffableThread();	//Создание потока 		mSecondThread.start();					//Запуск потока 		 		System.out.println("Главный поток завершён..."); 	} } 

В приведённом выше примере в методе main() создается и запускается еще один поток. Важно отметить, что после вызова метода mSecondThread.start() главный поток продолжает своё выполнение, не дожидаясь пока порожденный им поток завершится. И те инструкции, которые идут после вызова метода start(), будут выполнены параллельно с инструкциями потока mSecondThread.

Для демонстрации параллельной работы потоков давайте рассмотрим программу, в которой два потока спорят на предмет философского вопроса «что было раньше, яйцо или курица?». Главный поток уверен, что первой была курица, о чем он и будет сообщать каждую секунду. Второй же поток раз в секунду будет опровергать своего оппонента. Всего спор продлится 5 секунд. Победит тот поток, который последним изречет свой ответ на этот, без сомнения, животрепещущий философский вопрос. В примере используются средства, о которых пока не было сказано (isAlive() sleep() и join()). К ним даны комментарии, а более подробно они будут разобраны дальше.

class EggVoice extends Thread { 	@Override 	public void run() 	{ 		for(int i = 0; i < 5; i++) 		{ 			try{ 				sleep(1000);		//Приостанавливает поток на 1 секунду 			}catch(InterruptedException e){} 			 			System.out.println("яйцо!");	 		} 		//Слово «яйцо» сказано 5 раз 	} }  public class ChickenVoice	//Класс с методом main() { 	static EggVoice mAnotherOpinion;	//Побочный поток 	 	public static void main(String[] args) 	{ 		mAnotherOpinion = new EggVoice();	//Создание потока 		System.out.println("Спор начат..."); 		mAnotherOpinion.start(); 			//Запуск потока 		 		for(int i = 0; i < 5; i++) 		{ 			try{ 				Thread.sleep(1000);		//Приостанавливает поток на 1 секунду 			}catch(InterruptedException e){} 			 			System.out.println("курица!"); 		} 		 		//Слово «курица» сказано 5 раз  		if(mAnotherOpinion.isAlive())	//Если оппонент еще не сказал последнее слово 		{ 			try{ 				mAnotherOpinion.join();	//Подождать пока оппонент закончит высказываться. 			}catch(InterruptedException e){} 			 			System.out.println("Первым появилось яйцо!"); 		} 		else	//если оппонент уже закончил высказываться 		{ 			System.out.println("Первой появилась курица!"); 		} 		System.out.println("Спор закончен!");	 	} }  Консоль: Спор начат... курица! яйцо! яйцо! курица! яйцо! курица! яйцо! курица! яйцо! курица! Первой появилась курица! Спор закончен! 

В приведенном примере два потока параллельно в течении 5 секунд выводят информацию на консоль. Предсказать какой поток закончит высказываться последним невозможно. То есть попытаться, конечно, можно, и можно даже угадать, но есть большая вероятность того, что та же программа при следующем запуске будет иметь другого «победителя». Это происходит из-за так называемого «асинхронного выполнения кода». Асинхронность означает то, что нельзя утверждать, что какая-либо инструкция одного потока, выполнится раньше или позже инструкции другого. Или, другими словами, параллельные потоки независимы друг от друга, за исключением тех случаев, когда программист сам описывает зависимости между потоками с помощью предусмотренных для этого средств языка.

Теперь немного о завершении процессов…

Завершение процесса и демоны

В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main() уже завершился, но еще выполняются порожденные им потоки, система будет ждать их завершения.

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

Объявить поток демоном достаточно просто — нужно перед запуском потока вызвать его метод setDaemon(true);
Проверить, является ли поток демоном, можно вызвав его метод boolean isDaemon();

Завершение потоков

В Java существуют (существовали) средства для принудительного завершения потока. В частности метод Thread.stop() завершает поток незамедлительно после своего выполнения. Однако этот метод, а также Thread.suspend(), приостанавливающий поток, и Thread.resume(), продолжающий выполнение потока, были объявлены устаревшими и их использование отныне крайне нежелательно. Дело в том что поток может быть «убит» во время выполнения операции, обрыв которой на полуслове оставит некоторый объект в неправильном состоянии, что приведет к появлению трудноотлавливаемой и случайным образом возникающей ошибке.

Вместо принудительного завершения потока применяется схема, в которой каждый поток сам ответственен за своё завершение. Поток может остановиться либо тогда, когда он закончит выполнение метода run(), (main() — для главного потока) либо по сигналу из другого потока. Причем как реагировать на такой сигнал — дело, опять же, самого потока. Получив его, поток может выполнить некоторые операции и завершить выполнение, а может и вовсе его проигнорировать и продолжить выполняться. Описание реакции на сигнал завершения потока лежит на плечах программиста.

Java имеет встроенный механизм оповещения потока, который называется Interruption (прерывание, вмешательство), и скоро мы его рассмотрим, но сначала посмотрите на следующую программку:

Incremenator — поток, который каждую секунду прибавляет или вычитает единицу из значения статической переменной Program.mValue. Incremenator содержит два закрытых поля – mIsIncrement и mFinish. То, какое действие выполняется, определяется булевой переменной mIsIncrement — если оно равно true, то выполняется прибавление единицы, иначе — вычитание. А завершение потока происходит, когда значение mFinish становится равно true.

class Incremenator extends Thread { 	private boolean mIsIncrement = true; 	private boolean mFinish = false;  	public void changeAction()	//Меняет действие на противоположное 	{ 		mIsIncrement = !mIsIncrement; 	} 	public void finish()		//Инициирует завершение потока 	{ 		mFinish = true; 	}  	@Override 	public void run() 	{ 		do 		{ 			if(!mFinish)	//Проверка на необходимость завершения 			{ 				if(mIsIncrement)	 					Program.mValue++;	//Инкремент 				else 					Program.mValue--;	//Декремент 				 				//Вывод текущего значения переменной 				System.out.print(Program.mValue + " "); 			} 			else 				return;		//Завершение потока  			try{ 				Thread.sleep(1000);		//Приостановка потока на 1 сек. 			}catch(InterruptedException e){} 		} 		while(true);  	} }  public class Program { 	//Переменая, которой оперирует инкременатор 	public static int mValue = 0; 	 	static Incremenator mInc;	//Объект побочного потока  	public static void main(String[] args) 	{ 		mInc = new Incremenator();	//Создание потока 		 		System.out.print("Значение = "); 		 		mInc.start();	//Запуск потока 		 		//Троекратное изменение действия инкременатора 		//с интервалом в i*2 секунд 		for(int i = 1; i <= 3; i++) 		{ 			try{ 				Thread.sleep(i*2*1000); //Ожидание в течении i*2 сек. 			}catch(InterruptedException e){} 			 			mInc.changeAction();	//Переключение действия 		} 		 		mInc.finish();	//Инициация завершения побочного потока	 	} }  Консоль: Значение = 1 2 1 0 -1 -2 -1 0 1 2 3 4 

Взаимодействовать с потоком можно с помощью метода changeAction() (для смены вычитания на сложение и наоборот) и метода finish() (для завершения потока).

В этом примере показано, каким образом можно организовать взаимодействие между потоками. Однако есть одна проблема при таком подходе к завершению потока — Incremenator проверяет значение поля mFinish раз в секунду, поэтому может пройти до секунды времени между тем, когда будет выполнен метод finish(), и фактическим завершения потока. Было бы замечательно, если бы при получении сигнала извне, метод sleep() возвращал выполнение и поток незамедлительно начинал своё завершение. Для выполнения такого сценария существует встроенное средство оповещения потока, которое называется Interruption (прерывание, вмешательство).

Interruption

Класс Thread содержит в себе скрытое булево поле, подобное полю mFinish в программе Incremenator, которое называется флагом прерывания. Установить этот флаг можно вызвав метод interrupt() потока. Проверить же, установлен ли этот флаг, можно двумя способами. Первый способ — вызвать метод bool isInterrupted() объекта потока, второй — вызвать статический метод bool Thread.interrupted(). Первый метод возвращает состояние флага прерывания и оставляет этот флаг нетронутым. Второй метод возвращает состояние флага и сбрасывает его. Заметьте что Thread.interrupted() — статический метод класса Thread, и его вызов возвращает значение флага прерывания того потока, из которого он был вызван. Поэтому этот метод вызывается только изнутри потока и позволяет потоку проверить своё состояние прерывания.

Итак, вернемся к нашей программе. Механизм прерывания позволит нам решить проблему с засыпанием потока. У методов, приостанавливающих выполнение потока, таких как sleep(), wait() и join() есть одна особенность — если во время их выполнения будет вызван метод interrupt() этого потока, они, не дожидаясь конца времени ожидания, сгенерируют исключение InterruptedException.

Переделаем программу Incremenator – теперь вместо завершения потока с помощью метода finish() будем использовать стандартный метод interrupt(). А вместо проверки флага mFinish будем вызывать метод bool Thread.interrupted();
Так будет выглядеть класс Incremenator после добавления поддержки прерываний:

class Incremenator extends Thread { 	private boolean mIsIncrement = true;  	public void changeAction()	//Меняет действие на противоположное 	{ 		mIsIncrement = !mIsIncrement; 	}  	@Override 	public void run() 	{ 		do 		{ 			if(!Thread.interrupted())	//Проверка прерывания 			{ 				if(mIsIncrement) Program.mValue++;	//Инкремент 				else Program.mValue--;			//Декремент 				 				//Вывод текущего значения переменной 				System.out.print(Program.mValue + " "); 			} 			else 				return;		//Завершение потока	  			try{ 				Thread.sleep(1000);		//Приостановка потока на 1 сек. 			}catch(InterruptedException e){ 				return;	//Завершение потока после прерывания 			} 		} 		while(true);  	} }  class Program { 	//Переменая, которой оперирует инкременатор 	public static int mValue = 0; 	 	static Incremenator mInc;	//Объект побочного потока  	public static void main(String[] args) 	{ 		mInc = new Incremenator();	//Создание потока 		 		System.out.print("Значение = "); 		 		mInc.start();	//Запуск потока 		 		//Троекратное изменение действия инкременатора 		//с интервалом в i*2 секунд 		for(int i = 1; i <= 3; i++) 		{ 			try{ 				Thread.sleep(i*2*1000);		//Ожидание в течении i*2 сек. 			}catch(InterruptedException e){} 			 			mInc.changeAction();	//Переключение действия 		} 		 		mInc.interrupt();	//Прерывание побочного потока 	} }  Консоль: Значение = 1 2 1 0 -1 -2 -1 0 1 2 3 4 

Как видите, мы избавились от метода finish() и реализовали тот же механизм завершения потока с помощью встроенной системы прерываний. В этой реализации мы получили одно преимущество — метод sleep() вернет управление (сгенерирует исключение) незамедлительно после прерывания потока.

Заметьте что методы sleep() и join() обёрнуты в конструкции try-catch. Это необходимое условие работы этих методов. Вызывающий их код должен перехватывать исключение InterruptedException, которое они бросают при прерывании во время ожидания.

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

Метод Thread.sleep()

Thread.sleep() — статический метод класса Thread, который приостанавливает выполнение потока, в котором он был вызван. Во время выполнения метода sleep() система перестает выделять потоку процессорное время, распределяя его между другими потоками. Метод sleep() может выполняться либо заданное кол-во времени (миллисекунды или наносекунды) либо до тех пор пока он не будет остановлен прерыванием (в этом случае он сгенерирует исключение InterruptedException).

Thread.sleep(1500); 		//Ждет полторы секунды Thread.sleep(2000, 100);  	//Ждет 2 секунды и 100 наносекунд 

Метод join()

В Java предусмотрен механизм, позволяющий одному потоку ждать завершения выполнения другого. Для этого используется метод join(). Например, чтобы главный поток подождал завершения побочного потока myThready, необходимо выполнить инструкцию myThready.join() в главном потоке. Как только поток myThready завершится, метод join() вернет управление, и главный поток сможет продолжить выполнение.

Метод join() имеет перегруженную версию, которая получает в качестве параметра время ожидания. В этом случае join() возвращает управление либо когда завершится ожидаемый поток, либо когда закончится время ожидания. Подобно методу Thread.sleep() метод join может ждать в течение миллисекунд и наносекунд – аргументы те же.

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

Thinker brain = new Thinker(); 	//Thinker - потомок класса Thread. brain.start();		//Начать "обдумывание".  do { 	mThinkIndicator.refresh();		//mThinkIndicator - анимированная картинка.  	try{ 		brain.join(250);				//Подождать окончания мысли четверть секунды. 	}catch(InterruptedException e){} } while(brain.isAlive());	//Пока brain думает...  //brain закончил думать (звучат овации). 

В этом примере поток brain (мозг) думает над чем-то, и предполагается, что это занимает у него длительное время. Главный поток ждет его четверть секунды и, в случае, если этого времени на раздумье не хватило, обновляет «индикатор раздумий» (некоторая анимированная картинка). В итоге, во время раздумий, пользователь наблюдает на экране индикатор мыслительного процесса, что дает ему знать, что электронные мозги чем то заняты.

Приоритеты потоков

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

void setPriority(int priority) – устанавливает приоритет потока.
Возможные значения priority — MIN_PRIORITY, NORM_PRIORITY и MAX_PRIORITY.

int getPriority() – получает приоритет потока.

Некоторые полезные методы класса Thread

Это практически всё. Напоследок приведу несколько полезных методов работы с потоками.

boolean isAlive() — возвращает true если myThready() выполняется и false если поток еще не был запущен или был завершен.

setName(String threadName) – Задает имя потока.
String getName() – Получает имя потока.
Имя потока – ассоциированная с ним строка, которая в некоторых случаях помогает понять, какой поток выполняет некоторое действие. Иногда это бывает полезным.

static Thread Thread.currentThread() — статический метод, возвращающий объект потока, в котором он был вызван.

long getId()– возвращает идентификатор потока. Идентификатор – уникальное число, присвоенное потоку.

Заключение

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

Всех благ.

ссылка на оригинал статьи http://habrahabr.ru/post/164487/

Еще один взгляд на Entity Framework: производительность и подводные камни

Ни для кого не секрет, что адаптация Entity Framework проходит очень медленно. Огромное количество компаний продолжают использовать Linq2Sql и не планируют менять его на что-то новое в обозримом будущем, несмотря на то, что EF – официально рекомендуемая Microsoft технология доступа к БД, а Linq2Sql уже почти не поддерживается.

Тех, кто всё еще сомневается, можно ли использовать EF (и особенно – code first) на реальных проектах, приглашаю под кат.

Введение

Во-первых, если у вас за плечами уже пара выполненных проектов с использованием EF – дальше можно не читать. Вряд ли вы узнаете что-то новое.

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

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

Пост предполагает хотя бы общее знакомство с Entity Framework Code First, SQL и вообще принципами ORM.

Актуальной версией EF на момент написания является EF5. Для получения максимальной производительности был использован .NET 4.5. Все, сказанное ниже, относится в первую очередь к Code First с автоматической генерацией БД (который активно рекламируется самими сотрудниками Microsoft). Сервер БД – LocalDb (обычно работает медленнее, чем полноценный MS SQL Server).

Тестовый код доступен на GitHub.

Модель данных

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

У каждого пользователя (Account) есть несколько папок с письмами (MessageFolder). В каждой папке хранится несколько цепочек писем (MessageThread), при этом одна цепочка может находиться в множестве папок одновременно (ThreadsInFolders). Каждая цепочка состоит из множества писем. При этом цепочки шарятся между аккаунтами, письма – нет.

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

Она же в коде

public class Account     {         public int Id { get; set; }         [Required]         public string Name { get; set; }     }      public class Message     {         public int Id { get; set; }         public bool IsRead { get; set; }         public DateTime Date { get; set; }         public string Text { get; set; }          [Required]         public virtual MessageThread Thread { get; set; }         [Required]         public virtual Account Owner { get; set; }         [Required]         public virtual Account Sender { get; set; }         [Required]         public virtual Account Receiver { get; set; }     }      public class MessageThread     {         public int Id { get; set; }         [Required]         [StringLength(150)]         public string Subject { get; set; }          public virtual ICollection<MessageFolder> Folders { get; set; }     }      public class MessageFolder     {         public int Id { get; set; }         public string Name { get; set; }         [Required]         public Account Owner { get; set; }          public virtual ICollection<MessageThread> Threads { get; set; }     } 

Чтение данных

Самым главным в любой ORM-системе является чтение данных. Тут у EF проблем нет – запросы к БД ходят весело и задорно, поддерживается ленивая загрузка. Все загруженные объекты попадают во внутренний кэш контекста, затем могут быть запрошены напрямую из него (только через метод Find).

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

context.Messages.AsNoTracking().OrderBy(f => f.Id).Take(count)                 .Include(f => f.Owner).Include(f => f.Sender).Include(f => f.Receiver)                 .ToList(); 

SQL

SELECT TOP (100)  [Extent1].[Id] AS [Id],  [Extent1].[IsRead] AS [IsRead],  [Extent1].[Date] AS [Date],  [Extent1].[Text] AS [Text],  [Extent2].[Id] AS [Id1],  [Extent2].[Name] AS [Name],  [Extent3].[Id] AS [Id2],  [Extent3].[Name] AS [Name1],  [Extent4].[Id] AS [Id3],  [Extent4].[Name] AS [Name2] FROM    [dbo].[Message] AS [Extent1] INNER JOIN [dbo].[Account] AS [Extent2] ON [Extent1].[Owner_Id] = [Extent2].[Id] INNER JOIN [dbo].[Account] AS [Extent3] ON [Extent1].[Sender_Id] = [Extent3].[Id] INNER JOIN [dbo].[Account] AS [Extent4] ON [Extent1].[Receiver_Id] = [Extent4].[Id] ORDER BY [Extent1].[Id] ASC 

AsNoTracking отключает слежение EF за получаемыми объектами (они не будут кэшированы); Include – подгружает указанные связанные сущности (navigation property/свойства навигации) через генерацию, чаще всего, INNER JOIN; остальное – знакомый всем LINQ.

Самый интересный момент здесь – Include. Строго говоря, окончательное осознание того, что вам может понадобиться, происходит на уровне презентации, поэтому самое логичное расположение всех Include – там, либо на соседнем уровне (например, контроллеров в MVC). Фактически это означает, что вам придется протаскивать IQueryable<T> через всю структуру приложения, постоянно следя, чтобы никто не использовал методы, несовместимые с Linq2Entities, иначе это выльется в исключение. С классической архитектурой (репозитории-сервисы-контроллеры) такой подход мало приятен. Возможно, организация домена через запросы или extension methods к IQueryable поможет с этим, но я не пробовал.

Любой запрос кроме Find ходит к БД. Find делает это лишь в том случае, если сущность с необходимыми ключевыми полями не была найдена в локальном кэше контекста. То есть, из двух вызовов context.Messages.Find(1) на одном контексте, лишь первый из них выльется в запрос к БД. То же касается загрузки связанных сущностей. Например, в приведенном ниже коде владелец сообщения будет получен из кэша.

context.Accounts.Load(); //аккаунты были загружены в кэш где-нибудь в другом месте var m = context.Messages.First(); Console.WriteLine(m.Owner.Name); 

SQL

SELECT  [Extent1].[Id] AS [Id],  [Extent1].[Name] AS [Name] FROM [dbo].[Account] AS [Extent1] GO  SELECT TOP (1)  [c].[Id] AS [Id],  [c].[IsRead] AS [IsRead],  [c].[Date] AS [Date], [c].[Text] AS [Text],  [c].[Thread_Id] AS [Thread_Id],  [c].[Owner_Id] AS [Owner_Id],  [c].[Sender_Id] AS [Sender_Id],  [c].[Receiver_Id] AS [Receiver_Id] FROM [dbo].[Message] AS [c] 

Вполне логичный context.Messages.Include(f=>f.Owner).First() для избегания ленивой загрузки здесь бы лишь снизил производительность. Вывод: не стоит городить Include без предварительного изучения SQL-запросов, отправляемых к серверу.

Кроме того, если по условию задачи необходимо, чтобы в результаты попадали добавленные, но еще не сохраненные в БД данные, необходимо будет вручную проводить запрос к context.Set<T>().Local, а затем объединять его с данными, полученными из БД. Либо вызывать SaveChanges заранее. Еще один аргумент в пользу короткого времени жизни контекстов. Тут полная аналогия с Linq2Sql.

По поводу производительности: первый приведенный в этой части запрос для 1000 записей выполняется EF примерно в 2-3 раза медленнее, чем Linq2Sql. Однако, API загрузки намного приятнее, чем аналогичное в Linq2Sql.

Добавление данных

Добавление данных реализовано довольно просто. Любая добавленная через метод Add сущность просто добавляется во внутренний кэш контекста со статусом Added. При последующем вызове SubmitChanges проходит валидация, для всех подобных сущностей генерируются INSERT и они добавляются в БД. Все связанные navigation property, которые не были явно добавлены в контекст, так же принимаются как Added и добавляются в БД (что может служить источником довольно внезапных багов).

Основные советы по добавлению:

  1. При добавлении множества данных стоит отключить ValidateOnSaveEnabled и AutoDetectChangesEnabled. Это значительно поднимет производительность.
  2. Не стоит добавлять в одном контексте слишком много записей. Поскольку все они перед сохранением кэшируются в нем, большое количество объектов очень быстро вызывают проблемы с GC и количеством потребляемой памяти. В среднем, лучше пересоздавать контекст после каждых 100 записей.

Более подробно можно почитать на StackOverflow.
В плане производительность EF примерно равен, а то и обгоняет (после оптимизаций) Linq2Sql. Хотя, повторюсь, я не большой знаток Linq2Sql.

Модификация и удаление данных

Эти операции традиционно являются слабым место .NET ORM-ок. Главной проблемой является отсутствие поддержки Bulk-операций. То, что в SQL легко выражается через один запрос, ORM может сделать за тысячу.
Допустим, необходимо пометить все сообщения в цепочке как прочитанные. В EF это будет выглядеть подобным образом:

var messages = context.Messages.Where(f=>f.Thread.Id == threadId); foreach (var m in messages)     m.IsRead = true; context.SaveChanges(); 

SQL

SELECT  [Extent1].[Id] AS [Id],  [Extent1].[IsRead] AS [IsRead],  [Extent1].[Date] AS [Date],  [Extent1].[Text] AS [Text],  [Extent1].[Thread_Id] AS [Thread_Id],  [Extent1].[Owner_Id] AS [Owner_Id],  [Extent1].[Sender_Id] AS [Sender_Id],  [Extent1].[Reciever_Id] AS [Reciever_Id] FROM [dbo].[Message] AS [Extent1] WHERE [Extent1].[Thread_Id] = @p__linq__0  update [dbo].[Message] set [IsRead] = @0 where ([Id] = @1) --(N раз) 

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

Причины отсутствия множественных update/delete лично мне непонятны. И не только мне, поэтому существуют подобные мануалы и сторонние библиотеки.

С EntityFramework.Extended приведенный выше код будет выглядеть следующим образом:

var messages = context.Messages.Where(f=>f.Thread.Id == threadId); messages.Update(f => new Message {IsRead = true}); 

SQL

UPDATE [dbo].[Message] SET  [IsRead] = @p__update__0  FROM [dbo].[Message] AS j0 INNER JOIN ( SELECT  [Extent1].[Id] AS [Id] FROM [dbo].[Message] AS [Extent1] WHERE [Extent1].[Thread_Id] = @p__linq__0 ) AS j1 ON (j0.[Id] = j1.[Id]) 

Код выполняет всего один запрос к базе данных. Обратите внимание, что нет вызова SaveChanges – SQL выполняется сразу в обход контекста. Это может привести к некоторым неочевидным багам, когда контекст будет содержать данные о какой-либо записи, которая в действительности будет удалена/изменена.

Кроме того, библиотека генерирует не самый эффективные запросы, внутри довольно сильно опирается на dynamic и косвенно генерирует неимоверное количество исключений внутри фрэймворка (их видно, например, в IntelliTrace), тем самым представляя значительную базовую стоимость одного запроса. Пример в таблице ниже (EntityFramework без валидации).

Способ Модификация 10 записей, мс Модификация 10000 записей, мс
EntityFramework 8 935
EntityFramework.Extended 28 83

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

Коллекции как navigation property

От заказчика приходит просьба – давайте показывать возле имени каждой папки сообщений количество цепочек в ней. «Без проблем!» отвечает программист и быстро добавляет следующий код:

var count = folder.Threads.Count(); 

Как же хорошо, что существуют navigation properties, думает он, пока не видит генерируемый SQL.

SQL

SELECT  [Extent2].[Id] AS [Id],  [Extent2].[Subject] AS [Subject] FROM  [dbo].[ThreadsInFolders] AS [Extent1] INNER JOIN [dbo].[MessageThread] AS [Extent2] ON [Extent1].[MessageThread_Id] = [Extent2].[Id] WHERE [Extent1].[MessageFolder_Id] = @EntityKeyValue1 

Что в EF, что в Linq2Sql, вызов Count(), Any(), да и вообще любого запроса на коллекции, используемой как navigation property, приводит в полной загрузке этой коллекции в память. Причины можно понять, но об этой «фиче» зачастую забывают. Корректный запрос, считающий количество на уровне БД, выглядит так:

var count = context.Threads.Count(f => f.Folders.Any(e => e.Id == folder.Id))); 

SQL

SELECT  [GroupBy1].[A1] AS [C1] FROM ( SELECT  	COUNT(1) AS [A1] 	FROM [dbo].[MessageThread] AS [Extent1] 	WHERE  EXISTS (SELECT  		1 AS [C1] 		FROM [dbo].[ThreadsInFolders] AS [Extent2] 		WHERE ([Extent1].[Id] = [Extent2].[MessageThread_Id]) AND ([Extent2].[MessageFolder_Id] = @p__linq__0) 	) )  AS [GroupBy1] 

Но он требует, чтобы цепочки имели ссылку на папки, в которых они хранятся, что, с точки зрения нашей архитектуры, немного «не православный ООП».

Даты

Недавно на одном из наших проектов обнаружился интересный баг – на одном из серверов падало кэширование любых картинок, хранящихся в БД.

Исключение генерировалось на следующей строке:

Response.Cache.SetLastModified(lastUpdated); 

где lastUpdated – дата последней модификации картинки, получаемая прямо из БД.
В процессе дебага выяснилось, что ASP.NET MVC внутри себя вызывал на полученной дате ToUniversalTime(), и затем падал на проверке:

if (utcDate > DateTime.UtcNow) {                 throw new ArgumentOutOfRangeException("utcDate");              } 

Причина бага довольно проста – при сохранении в БД, DateTime теряет значение свойства Kind и при получении данных из БД получает DateTimeKind.Undefined (логичное значение, придуманное как раз для таких случаев, хотя лично я считаю хранение чего-либо кроме UTC в БД моветоном). Если вы используете даты только в собственном, полностью контролируемом коде, то это не имеет особого значения (поскольку большинство операций не учитывают DateTimeKind), но любой вызов ToUniversalTime() или ToLocalTime() сдвинет это время в зависимости от локальных настроек системы. Так, наше кэширование падало только если часовой пояс был GMT — N.

Подобное поведение характерно как для EF, так и для Linq2Sql и, строго говоря, никак не настраивается. Существуют разнообразные способы решения проблемы, но пользователям code first придется туго. Решение через подписку на событие ObjectMaterialized может отсрочить уничтожение контекста (если оно производится не руками) и привести к совсем неочевидным багам. Есть еще возможность использовать простенький враппер вокруг DateTime, и маппить его в таблицу как ComplexType, но придется наслаждаться всеми прелестями даты как reference type.

RequiredAttribute

Мой любимый подводный камень и главная причина моего разочарования в EF.

Напомню, что в сущности письма свойства Owner, Sender, Receiver и Thread помечены как Required. Во-первых, это указывает генератору БД на то, что поля должны быть помечены как NOT NULL, а во-вторых, включает на них внутреннюю валидацию EF.

Что, по-вашему, выполнит следующий код при включенной валидации на только что созданном контексте?

var message = context.Messages.Find(1); message.IsRead = true; context.SaveChanges(); 

Если вы решили, что он отметит письмо как прочитанное — вы ошиблись. Появится DbEntityValidationException.

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

Путей решения несколько: можно подгружать все значения заранее (сложно), написать свой атрибут (велосипедно), использовать try-catch вокруг SaveChanges и подгружать все связи в catch (медленно), отключать валидацию, когда надо (опасно), а можно просто отказаться от RequiredAttribute и определить NOT NULL через Fluent API. Последнее, правда, сменит тип исключения, выбрасываемого при попытке добавить новую запись с кривыми данными, отключит валидацию на уровне кода, да и внесет довольно неочевидное поведение в модификацию существующих записей (любая установка Required свойств в null просто не будет попадать в UPDATE запрос). В любом случае, если эта проблема обнаружилась довольно поздно в цикле разработке, приятной она не окажется.

Выводы (tl;dr)

Можно ли использовать EF для чего-то кроме блога с одним посещением в месяц? Решать вам. Всё не так радужно, как Microsoft пытается нам рассказать и любой более-менее нагруженный проект потребует значительных усилий по оптимизации и исправлению недостатков самого EF.

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

Есть ли преимущества у EF по сравнению с Linq2Sql? Решать, опять же, вам. Для меня главным преимуществом является возможность автогенерации БД через code first (он и правда очень приятен), но количество его ограничений на данный момент не поддается счету. Нет адекватной поддержки представлений, хранимых процедур и функций (должны поправить в EF6). Невозможно адекватно использовать в коде автоматически сгенерированную таблицу для связи много-ко-многим (можно определить её в коде самостоятельно, но это значительно убавит удобство работы с собственными классами) и т.д. Идеальных ORM-средств не бывает и EF – не исключение.

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

ссылка на оригинал статьи http://habrahabr.ru/post/164483/

Обработка. Подготовка для сайта «четких» фотографических изображений. Утилита для обработки по алгоритму «Least-Squares Image Resizing Using Finite Differences»

Для перфекционистов фотографии.
Обычно большая фотография позволяет видеть больше деталей.

Но в этом примере — наоборот: меньшая фотография кажется более четкой?

Не подтасовывал результат: для уменьшения большей из этих двух фотографий был использовано метод Bicubic Shaper — тот, что рекомендуется в Фотошопе именно для уменьшения изображений.

Значит, при подготовке меньшего размера фотографии был использован много более эффективный алгоритм?

Фотографии, публикуемые на веб-сайтах обычно подвергают 2 изменениям: уменьшению разрешения и увеличения четкости (известно под термином «шарп» или «шарпенинг» — это калька с английского sharp).

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

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

Недавно набрел на очень эффективный метод, описанный в документе A. Muñoz Barrutia, T. Blu, M. Unser, «Least-Squares Image Resizing Using Finite Differences,» IEEE Transactions on Image Processing, vol. 10, no. 9, pp. 1365-1378, September 2001. bigwww.epfl.ch/publications/munoz0101.html (слева ссылка Download\PDF)

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

Обнаружил единственную реализацию этого метода на Java в качестве плугина для программы ImageJ.
bigwww.epfl.ch/algorithms/ijplugins/resize/

Как установить:

1. Ставим Java java.com/ru/download/index.jsp
2. Ставим ImageJ rsb.info.nih.gov/ij/
3. Скачиваем со страницы bigwww.epfl.ch/algorithms/ijplugins/resize/ файл bigwww.epfl.ch/algorithms/ijplugins/resize/Resize_.jar и не распаковывая его помещаем в подкаталог plugins программы ImageJ
4. Изменять размер и шарпить можно в ImageJ через меню «Plugins\Resize\Resize», выбрав в появившемся диалоговом окне метод «Least-Squares».

Замечание: Можно применять только с 8-битными изображениями. На 16-битных не все хорошо. Но поскольку цель — подготовка для веба, то этого более, чем достаточно.

Наш умелец написал программу для более простого использования данного плугина под Windows. Называется эта программа «ImageJ Resize Launcher» (но и сам плугин и ImageJ и Java должны быть установлены). На момент написания этого текста последний релиз программы имеет версию 2.2 и имеется пререлиз 3. Официального сайта программа не имеет, но легко находится через поисковики по названию.

Для иллюстрации статьи использована «бесплатная фотография недели» отсюда www.shutterstock.com/gallery-449524p1.html

Внимание!!! Вопрос к аудитории!!!

Есть ли другие реализация этого метода кроме плугина к ImageJ?
Где еще можно прочитать про этот алгоритм???

ссылка на оригинал статьи http://habrahabr.ru/post/164473/

Рисуем снежинки с помощью SVG

image

Идею создания снежинок использованную в этом посте я позаимствовал из детского сада. Там складывают лист бумаги в несколько раз, вырезают ножницами дырочки и после разворачивания получают снежинку. В данном случае нам потребуется нарисовать одну шестую и потом её копии повернуть на 60 градусов 5 раз.

Такие снежинки можно использовать в новогодних поздравлениях. Для затравки я сделал вот такую открытку.

Для рисования можно использовать группу элементов (g) или один элемент path

<path class="templateSnowflake" id="tplSnowflake1" d="   M0,0 L40,40   M 40,30 L 30,30 L 30,40   M 35,25 L25,25 L 25,35   M 35,20 L20,20 L 20,35   M 40,11 L15,15 L 11,40   M 20,8 L8,8 L 8,20 " /> 

в данном случае у нас есть одна основная линия (M0,0 L40,40) и дополнительные.

Для сборки снежинки сделаем группу:

<g id="snowFlake1">   <use xlink:href="#tplSnowflake1" />   <use xlink:href="#tplSnowflake1" transform="rotate(60)" />   <use xlink:href="#tplSnowflake1" transform="rotate(120)" />   <use xlink:href="#tplSnowflake1" transform="rotate(180)" />   <use xlink:href="#tplSnowflake1" transform="rotate(240)" />   <use xlink:href="#tplSnowflake1" transform="rotate(300)" /> </g> 

Использовать снежинку можно следующим образом:

<use xlink:href="#snowFlake1" /> 

Я уже сделал несколько снежинок, а какие снежинки получатся у Вас?

ссылка на оригинал статьи http://habrahabr.ru/post/164477/

Быстрый старт с Google Test


Google Test — это фреймворк от Google для юнит-тестирования кода на С++. Общей архитектурой он слегка напоминает общепринятые boost::test и CppUnit, хотя слегка отличается в деталях (как по мне — в лучшую сторону). Большая обзорная статья этого фреймворка уже как-то пробегала на Хабре, но нынче она в каком-то побитом состоянии (код не отображается), да и кажется мне слишком сложной для начала работы. Поэтому я коротко опишу «Hello world» на Google Test, указав на несколько потенциальных проблем, с которыми вы можете столкнуться, используя Google Test при разработке под Visual Studio.

Сборка

  1. Загружаем архив с кодом, разархивируем.
  2. В папке gtest-1.6.0\msvc есть два файла: gtest.sln и gtest-md.sln. Это файлы решений (Solution) Visual Studio. Отличаются они опциями сборки: gtest.sln собирает код с ключем /MT, а gtest-md.sln с ключем /MD. Если вы не знаете, за что отвечают эти ключи — можете почитать, к примеру, тут или тут. Вы должны скомпилировать тот же вариант, с которыми собирается проект, который вы собираетесь тестировать. Это важно, иначе получите кучу невразумительных ошибок линкера. Проверить, с какими ключами собирается ваш проект можно вот тут:

    Код Google Test успешно собирается Visual Studio 2008\2010 (другими не пробовал). На выходе вы получите файлы gtestd.lib\gtest.lib (для дебаг и релиз конфигураций). Со сборкой на этом всё.

Hello world

  1. Открываем Solution, который вы собираетесь тестировать. Добавляем в него новый проект (консольное С++ приложение).
  2. В этот проект добавляем зависимость от скомпиленных на втором шаге библиотек gtestd.lib\gtest.lib, путь к include-папке Google Test, зависимости к тем проектам в вашем решении, которые вы собираетесь тестировать.

  3. Пишем в главном файле тестового проекта следующий код:
    #include "stdafx.h" #include "gtest/gtest.h"  class CRectTest : public ::testing::Test { };  TEST_F(CRectTest, CheckPerimeter)  { 	CSomeRect rect; 	rect.x = 5; 	rect.y = 6; 	ASSERT_TRUE(rect.GetPerimeter() == 30); }  int main(int argc, char **argv) { 	::testing::InitGoogleTest(&argc, argv); 	return RUN_ALL_TESTS(); } 

    Здесь мы тестируем некий класс прямоугольника на правильность вычисления площади.

  4. Запускаем тестовый проект. Видим следующее:

Грабли

Номер один

Не ошибитесь с выбором компилируемого решения на втором шаге. Если ошибетесь и забудете — выяснить в чём ошибка позже будет фактически не реально.

Номер два

Если вы планируете разнести основное тестовое приложение и сами тесты по разным проектам, вы столкнётесь с одной хитрой проблемой. Дело в том, что гугловские юнит-тесы по сути являются статическими классами и компилятор Visual C++ из-за имеющегося нём бага попросту выкинет эти классы по ходу компиляции. Для избежания этого бага нужно выкрутиться способом, описанным вот тут.

Номер три

Не забывайте, что тестируемые статические библиотеки нужно не достаточно добавить в зависимости (Dependencies) тестового проекта, их нужно добавить в ссылки (References), иначе получим ошибки линковки.

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

Чуть более глубокая статья на Хабре
Быстрый старт в родной документации
Часто задаваемые вопросы
Продвинутое использование фреймворка
Плагин к Visual Studio для запуска тестов

Успехов в тестировании.
С Новым Годом!

ссылка на оригинал статьи http://habrahabr.ru/post/164471/