Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

JDK 6 Update 5 is out

Download JDK 6 Update 5.

Spinlock explained

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

Механизм работы spinlock

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

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

Реализация 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

Спин блокировку следует использовать исключительно:

  1. Для синхронизации действительно маленьких кусков исходного кода.

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

  2. На многопроцессорной машине.

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

Proof of concept

Следующим кодом я проверял корректность работы 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 U20 review

Недавно припекло купить новый плеер. Я, как поклонник продукции 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 наблюдаются следующие проблемы:

  1. Плеер не виден, как обычный диск. Тоесть, оно выглядит, как подключенный телефон Nokia.
  2. Постоянно после нескольких операций записать больше ничего не удается - выдает ошибку о том, что устройство отключено:

    [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.

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

  1. При ежедневном аккуратном использовании аккумулятор умрет за полгода-год.
  2. Купить в Украине аккумулятор невозможно. Та же проблема существует с моим H320. Причем для него есть много альтернативных батарей емкостью в 2200 mAh, но у нас они не продаются.
  3. Замена аккумулятора в СЦ стоит дорого. Для H320 это 50$. Для Clix 2 такой вожможности может не быть вообще!

Также Clix 2 заряжается только от mini-USB, поэтому лучше сразу искать USB Charger...

Пока все! ЗЫ: Доволен, как слон!

Как получить загранпаспорт в Украине (с матами)

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

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

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

Очередь. Она отражена в списке. Список поддерживается людьми, которые находятся в его начале. Активный размер списка - 500 и более человек. Каждый день в 18:30 выполняется перекличка по всему списку! Кого нет - вычеркивают. В помещение, понятное дело, пускают строго по списку.

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

Простите за ненормативную лексику, но это просто пиздец.

Oracle Will Be First

Why Oracle will be always first?

  • First commercial RDBMS
  • First 32-bit database
  • First database with read consistency
  • First client-server database
  • First SMP database
  • First 64-bit RDBMS
  • First Web database
  • First database with Native Java Support
  • First commercial RDBMS ported to Linux
  • First database with XML
  • First database with Real Application Clusters (RAC)
  • First True Grid database
  • Free Oracle Database (Oracle Express Edition)
  • Unbreakable Linux Support

Marshal NULL for a pointer to a struct

Памаретрами многих функций WinAPI являются указатели на структуры или простые интегральные типы. Часто функция позволяет опускать такие параметры путем передачи NULL. Как тогда быть с .NET маршалингом если структура не является указателем? Два решения далее...

Прототип тестируемой функции следующий:

HANDLE WINAPI CreateSemaphore(
  __in_opt  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  __in      LONG lInitialCount,
  __in      LONG lMaximumCount,
  __in_opt  LPCTSTR lpName
);

NULL попытаемся передавать в lpSemaphoreAttributes.

Решение 1 - передача NULL в лоб

Известно, что размер указателя фиксирован для определенной архитектуры. На 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 и является.

Решение 2 - unsafe код

[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> решила бы эту проблему.

Copyright 2007-2011 Chabster