Большинство разработчиков пишут сложные программные комплексы на языках 3-го поколения. При этом их знания
начинаются с языка и заканчиваются библиотеками для этого языка. Что происходит за кулисами
- страшная тайна,
доступная лишь немногим. Еще пару лет назад многопоточность и виртуальная память у меня асоциировались с космосом, о
котором не хочется думать потому, что страшно и совершенно непонятно. Сейчас же я имею хорошее представление о том,
как выполняются мои программы - от трансляции исходного кода в ассемблерные инструкции до цифрового логического
уровня. Хочу сказать, что ничего страшного или сверхестественного нет ни в ассемблере, ни в операционной системе, ни
даже в механизме работы процессора. Ну, и продемонстрирую это на примере реализации простейшего примитива
синхронизации потоков под названием spinlock.
Блокировка может находится в двух состояниях - свободна и захвачена. Ее состояние должно быть отражено в ячейке памяти, доступной двум или более потокам. Соответственно, лучшего места, чем оперативная память, для нее найти невозможно. Поток, который пытается захватить блокировку, обязан ждать, пока она не освободится. Ожидание представляет собой цикл попыток захватить блокировку и прерывание цикла в случае успеха. И никакие два потока не должны захватить блокировку одновременно.
Последнее предложение предыдущего абзаца диктует требование к операции захвата блокировки - она должна быть атомарной. Поскольку, грубо говоря, атомарной операцией является одна ассемблерная инструкция, то и захват блокировки должен выполнятся одной такой инструкцией. При этом, одним из аргументов должен быть адрес памяти, где хранится состояние блокировки.
Код функций (объяснение дальше):
__declspec(naked) void __fastcall SpinLock(int *cookie) { __asm { mov eax, 1 spinLoop: lock xchg eax, [ecx] test eax, eax jnz spinLoop ret } } __declspec(naked) void __fastcall SpinUnlock(int *cookie) { __asm { mov eax, 0 lock xchg eax, [ecx] ret } }
Код функций состоит исключительно из ассемблерных инструкций, поэтому они объявлены, как
__declspec(naked)
. Конвенция вызова __fastcall
подарит нам единственный аргумент (адрес
состояния блокировки) в регистре ecx
. xchg
- атомарная инструкция, которая в нашем случае
меняет местами значение в регистре eax
(где всегда хранится единица) и по адресу памяти, который хранится
в ecx
. В результате выполнения одной неделимой инструкции, мы получим в eax
нолик, если
блокировка успешно захвачена. Состояние блокировки изменится соответственно. Если блокировка занята, в eax
будет находится единица. Оставшиеся инструкции проверяют регистр eax
и реагируют на успех или провал
захвата блокировки.
Вы, наверное, не заметили, что в коде используется не xchg
, а lock xchg
. Это потому, что у
меня многопроцессорная машина и две инструкции xchg
могут выполнятся одновременно. Чтобы их
синхронизировать, применяется конструкция lock
, которая заставляет блокировать шину памяти на время
выполнения инструкции.
Спин блокировку следует использовать исключительно:
Поток, ждущий освобождения блокировки, выполняет бесполезную работу, соответственно, расходует процессорное время. Основная цель - потребить меньше ресурсов, чем потребуется для использования примитивов синхронизации операционной системы, тоесть для переключения контекста выполнения.
Поскольку на однопроцессорной машине два потока не могут выполнятся одновременно, поток, который владеет блокировкой, во время попыток ее захвата другим потоком приостановлен и не выполняет никаких действий по освобождению ресурса. Вообще, можно добиться отсутствия переключений контекста потока в момент, когда блокировка захвачена. Но, это очень сложно контролировать.
Следующим кодом я проверял корректность работы spinlock:
// CppTest.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <Windows.h> __declspec(naked) void __fastcall SpinLock(int *cookie) { __asm { mov eax, 1 spinLoop: lock xchg eax, [ecx] test eax, eax jnz spinLoop ret } } __declspec(naked) void __fastcall SpinUnlock(int *cookie) { __asm { mov eax, 0 lock xchg eax, [ecx] ret } } int lockCookie = 0; int i = 0; int j = 0; DWORD WINAPI Thread1(LPVOID lpParameter) { while (true) { SpinLock(&lockCookie); i++; j++; if (i != j) DebugBreak(); SpinUnlock(&lockCookie); } } DWORD WINAPI Thread2(LPVOID lpParameter) { while (true) { SpinLock(&lockCookie); i++; j++; if (i != j) DebugBreak(); SpinUnlock(&lockCookie); } } int _tmain(int argc, _TCHAR* argv[]) { InterlockedIncrement(reinterpret_cast<volatile LONG*>(&argc)); DWORD thread1Id; DWORD thread2Id; HANDLE hThread1 = CreateThread(NULL, 100, Thread1, NULL, 0, &thread1Id); HANDLE hThread2 = CreateThread(NULL, 100, Thread2, NULL, 0, &thread2Id); Sleep(INFINITE); return(0); }
Кстати, глянув на ассемблерный код функции InterlockedIncrement
я убедился, что на процессорах
Core 2 Duo нужно использовать блокировку шины памяти. Как и на многопроцессорных станциях.
А затем, что ассемблерный код не является переносимым даже в пределах одной архитектуры! Скажем, будь у меня
однопроцессорная машина, я бы не использовал конструкцию lock
и мой код бы не работал корректно на
двуядерном процессоре! Кстати, просматривая исходники Windows 2000 я заметил, что реализация всех Interlocked
операций завязана на архитектуре и мультипроцессорности. Отсюда и непереносимость утановленной винды на другую
платформу. Версий ядра то несколько!
Недавно припекло купить новый плеер. Я, как поклонник продукции iRiver, решил купить... правильно, очередной iRiver. Первым у меня был iRiver iMP-550, потом я купил iRiver H320. Сейчас мне понравилась новинка под названием iRiver Clix 2 или просто iRiver U20.
Недели две прозванивал киевские магазины на предмет черного девайса! Везде только красные. Самое печальное, что в прайсах цвет не указывают. На сайте картинка черного, а продают красный. Впрочем, лучше обьяснить подробнее.
Цветов у iRiver U20 я видел минимум 4 - черный, белый, красный и черный с красной полоской (Red Line). Вероятно, цветов больше, но Гугление картинок или подтверждений не находит. Я приобрел Red Line:
Комплектацию описывать не буду. Первая зарядка длится ~3 часа. К Windows XP плеер подключился без проблем, с Windows Vista ждала небольшая засада - плеер не находился. Помогло изменение настройки Settings -> Advanced -> Connection Type со стандартного значения MCS(UMS) на MTP. Плеер при этом форматируется и теряются все стандартные файлы, в том числе темы!
Прошивка обновилась без проблем. Запись происходит не шибко быстро (я привык к 9 метрам в секунду на H320). На Windows Vista наблюдаются следующие проблемы:
оновыглядит, как подключенный телефон Nokia.
[Window Title]
Error Creating Folder
[Content]
The new folder could not be created in this location. The device has either stopped responding or has been disconnected.
[OK]
Дисплей просто обалдеть - очень сочный. Звук, стандартно, на высоком уровне. Управление непривычное, одной рукой по
началу трудно - постоянно нажимаю правую кнопку вместо левой или верхней. Из-за такого нестандартного решения возникает
впечатление, что верхняя крышка люфтит
. Следует сразу приучить себя ставить iRiver U20 на Hold,
поскольку в кармане кнопки нажимаются часто и легко. Долго не мог найти настройку режим воспроизведения!
Оказалось, нужно в режиме Now playing зажать на пару секунд правую кнопку... Чертовски логично!
За аддонами следует идти на сайт ClixHere.
Обращаю внимание, что у плеера встроенный аккумулятор. Это означает следующее:
умретза полгода-год.
Также Clix 2 заряжается только от mini-USB, поэтому лучше сразу искать USB Charger...
Пока все! ЗЫ: Доволен, как слон!
Посвящается всем, кто возжелал получить загранпаспорт...
Не перестаю поражаться повседневному долбоебизму в нашей замечательной стране. Все знают, что такое паспортный стол. Он всегда был полным пиздецом, а сейчас этот пиздец стал еще большим пиздецом.
Итак, организации, оказывающие помощь в получении заграничного паспорта прекратили свою деятельность. Спасибо тебе, Маша, что уговорила сделать нам загранки летом. Теперь единственный легальный способ получить этот документ - паспортный стол. Я не буду говорить о работниках паспортных столов, все и так знают, что это хамовитые лентяи, получающие наслаждение от сжигания нервов здорового населения. Расскажу, что придумало это уже не очень здоровое население...
Очередь. Она отражена в списке. Список поддерживается людьми, которые находятся в его начале. Активный размер списка - 500 и более человек. Каждый день в 18:30 выполняется перекличка по всему списку! Кого нет - вычеркивают. В помещение, понятное дело, пускают строго по списку.
Мне даже тяжело сказать, кого следует расстрелять в первую очередь - трудяг в паспортном столе или отчаянных людей, придумавших этот цирк.
Простите за ненормативную лексику, но это просто пиздец.
Why Oracle will be always first?
Памаретрами многих функций WinAPI являются указатели на структуры или простые интегральные типы. Часто функция
позволяет опускать такие параметры путем передачи NULL
. Как тогда быть с .NET маршалингом если
структура не является указателем? Два решения далее...
Прототип тестируемой функции следующий:
HANDLE WINAPI CreateSemaphore( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, __in LONG lInitialCount, __in LONG lMaximumCount, __in_opt LPCTSTR lpName );
NULL
попытаемся передавать в lpSemaphoreAttributes
.
в лоб
Известно, что размер указателя фиксирован для определенной архитектуры. На x86 он равен 4-м байтам. Грубо говоря,
указатель - это число. В .NET существует специальный тип, представляющий машинный указатель, - IntPtr
.
Кстати, его ввели потому, что размер int
(он же Int32
) фиксирован и на любой платформе равен
4-м байтам, а размер IntPtr
зависит от платформы. Этим мы и воспользуемся для передачи NULL:
[DllImport("kernel32.dll", SetLastError = true)] static public extern IntPtr CreateSemaphore(IntPtr lpSemaphoreAttributes, Int32 lInitialCount, Int32 lMaximumCount, String lpName); static private void Main(string[] args) { IntPtr semaphore = CreateSemaphore(IntPtr.Zero, 0, 10, "TestSemaphore"); if (semaphore == IntPtr.Zero) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } }
На низком уровне типы LPSECURITY_ATTRIBUTES
и IntPtr
одинаковы и представляют собой
машинный указатель. Поэтому можно смело передавать машинный NULL
, которым IntPtr.Zero
и
является.
[StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public Int32 nLength; public IntPtr lpSecurityDescriptor; public Int32 bInheritHandle; } [DllImport("kernel32.dll", SetLastError = true)] static public extern unsafe IntPtr CreateSemaphore(SECURITY_ATTRIBUTES* lpSemaphoreAttributes, Int32 lInitialCount, Int32 lMaximumCount, String lpName); static private unsafe void Main(string[] args) { IntPtr semaphore = CreateSemaphore(null, 0, 10, "TestSemaphore"); if (semaphore == IntPtr.Zero) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } }
Этот вариант отличается типом параметра lpSemaphoreAttributes
- SECURITY_ATTRIBUTES*
,
который теперь в точности совпадает с объявлением WinAPI. Соответственно, можно использовать null
в
качестве значения этого параметра.
Первое, что мне захотелось сделать - объявить lpSemaphoreAttributes
, как
Nullable<SECURITY_ATTRIBUTES>
. Это ведь чертовски логично! Но, нет, не судьба - Cannot marshal
'parameter #1': Generic types cannot be marshaled.
Далее, я попытался использовать следующую сигнатуру:
[DllImport("kernel32.dll", SetLastError = true)] static public extern IntPtr CreateSemaphore(ref SECURITY_ATTRIBUTES lpSemaphoreAttributes, Int32 lInitialCount, Int32 lMaximumCount, String lpName);
Вот только код, инициализирующий структуру lpSemaphoreAttributes
в пустой набор, анологичный передаче
NULL
, получился большой и некрасивый, а структура, заполненная нулями, не годится. null
,
понятное дело, передать нельзя.
В результате остановился на двух вышеописанных решениях. Правда, они хороши лишь для случая, когда нужно передать
NULL
. Второй вариант, к тому же, напрягает использованием
unsafe кода, хотя, фактически, меняется
только сигнатура метода. Оба варианта вносят непростительную сложность при передаче не-NULL
значения.
Получается, для удобного использования функции с множеством параметров нужно создавать множество функций с
подходящими для конкретного случая сигнатурами! И банальная поддержка маршалинга Nullable<T>
решила
бы эту проблему.