пятница, 4 марта 2011 г.

Почему плохо использовать lock(this) в C# ??


На днях на в фирме встал вопрос "Почему нельзя использовать lock(this) для синхронизации потоков?". На мелкомягком MSDN про это пишут так:

lock (this) может привести к проблеме, если к экземпляру допускается открытый доступ.
Возьмем простенький пример:

У нас есть некоторый объект, который асинхронно(в отдельном потоке) проверяет сам себя и каждые 3 секунды выдает на консоль сообщение "Все в порядке...продолжаю диагностику". Чтобы было поинтереснее, дадим нашему объекту имя и количество денег.

public class AutoTestingObj
{
protected String m_name;
protected int m_cash;

public String Name
{
  get
    {
       return m_name;
    }
    set
    {
       m_name = value;
    }
}

public int Cash
{
  get
    {
       return m_cash;
    }
    set
    {
       m_cash = value;
    }
}
}


Теперь добавим логику тестирования в отдельном потоке. При этом нужно как-то фиксировать переменные m_cash и m_name, чтобы никто из другого потока их не изменил во время тестирования. На первый взгляд логичнее и проще всего залочить весь объект и да будет на счастье. Код выглядит так:

public class AutoTestingObj
{
Thread m_thread;

protected String m_name;
protected int m_cash;

public String Name
{
  get
{
    return m_name;
    }
    set
    {
       m_name = value;
    }
}

public int Cash
{
  get
    {
       return m_cash;
    }
    set
    {
       m_cash = value;
    }
 }
public AutoTestingObj()
 {
    m_thread = new Thread(this.TestSystem);
}

public void Start()
{
  m_thread.Start();
}

public void TestSystem()
{
  for (; ; )
    {
       //лочим весь объект, чтобы никто не изменил
       lock (this)
       {
          //проводим какое-то тестирование в течении 3 секунд
          Thread.Sleep(3000);
          Console.WriteLine("Все в порядке...продолжаю диагностику");
       }
    }
}
}

Вроде бы все прекрасно. Обезопасили объект от многопоточного произвола. Правда мы не заметили нескольких неприятных моментов.
  1. Мы заблокировали объект для других потоков. Если этот объект понадобиться в другом потоке, надо будет ждать пока объект освободиться. Не очень удобно. Логичнее было бы добиться запрета на изменение(запись) полей, а чтение разрешить.
  2. Мы обезопасили объект только в случаем, когда везде перед переменной ставим lock(...). Вообще говоря, ничего мы этим способом не обезопасили, свойства Name и Cash легко можно изменить из любого потока обратившись к ним вне lock блока.
  3. Самая интересная причина. Если другой наш коллега захочет использовать экземпляр класса для синхронизации потоков, его ожидает бооольшой сюрприз. Поясню суть на примере. Написана вот такая программа

static void Main(string[] args)
{
AutoTestingObj inst = new AutoTestingObj();
inst.Start();

Thread.Sleep(100);

Console.WriteLine("Запланированна работа на 5 секунд");

DateTime start = DateTime.Now;
lock (inst)
{
  Thread.Sleep(5000);
}
TimeSpan delta = DateTime.Now - start;

Console.WriteLine("Работа выполнялась {0} секунд", delta.TotalSeconds);
}


Сколько выполнялась работа? Ответ: примерно 7.9 секунды(думаю, понятно откуда такой результат). А чтобы все было совсем хорошо, представим что у автора программы нету исходников нашего класса AutoTestingObj. Не хотел бы я оказаться в таком положении.

    Чтобы избежать этих трех неприятностей лучше действовать по хорошо известной схеме: залачивать можно только private члены класса. Например так:

    public class Locker { }

    public class AutoTestingObj
    {
    Thread m_thread;

    private Locker m_locker; //Locker-экземпляр любого класса(не путать со struct)

    protected String m_name;
    protected int m_cash;

    public String Name
    {
      get
        {
           return m_name;
        }
        set
        {
           lock (m_locker)
           {
              m_name = value;
           }
        }
    }

    public int Cash
    {
      get
        {
           return m_cash;
        }
        set
        {
           lock (m_locker)
           {
              m_cash = value;
           }
        }
    }

    public AutoTestingObj()
    {
      m_locker = new Locker();
        Name = "No name";   
        m_thread = new Thread(this.TestSystem);
    }

    public void Start()
    {
      m_thread.Start();
    }

     public void TestSystem()
    {
      for (; ; )
        {
           lock (m_locker)
           {
              Thread.Sleep(3000);
              Console.WriteLine("Все в порядке...продолжаю диагностику");
           }
        }
    }
    }

    Все 3 проблемы решили. Но стоит помнить, что присвая новое значение свойству Name или Cash поток будет ждать пока освободиться m_locker.

    воскресенье, 20 июня 2010 г.

    OpenCV + XCode

    Про то, что такое OpenCV можно почитать здесь http://ru.wikipedia.org/wiki/OpenCV.
    Сегодня попытался подключить OpenCV 2.1 к XCode 3.2.1. Оказалось, что есть много подводных камней, про которые мало где пишут. Существует, по крайней мере 2 способа.
    Способ раз.
    Проще всего скачать primery framework например отсюда http://www.ient.rwth-aachen.de/cms/opencv/. Копируем OpenCV.framework в папку /Library/Frameworks. На самом деле это не обязательно. В XCode создадим новый Command Line Tool проект, язык программирования выбираем C++ stdc++
    Назовем проект FirstOpenCV. Жмем правой кнопкой мыши на проект, выбираем Add -> Existing Framework
    Выбираем в списке OpenCV.framework. Если его нет в списке, щелкаем по кнопке “Add other ...” вручную находим OpenCV.framework и жмем Add. В общем-то и все))) библиотека подключена и можно ей пользоваться. Хедеры подключаем вот так
    #include "OpenCV\нужный_хедер.h"
    Казалось бы все просто и логично, однако есть большая такая ложка неприятностей. Возмем простенькую функцию для просмотра видео.
    void ShowVideo(char* fileName)
    {
            cvNamedWindow(videoWindowName, CV_WINDOW_AUTOSIZE);
            CvCapture* capture = cvCreateFileCapture(fileName);
            
            IplImage* frame;
            for (; ; )
            {
                    frame = cvQueryFrame(capture);
                    if (!frame) break;
                    cvShowImage( videoWindowName, frame );
                    char c = cvWaitKey(33);
                    if (c==27) break;
                    printf("%c", c);
            }
            cvReleaseCapture(&capture);
            cvDestroyWindow(videoWindowName);
    }
    Проблема 1: При запуске видео очень дергается и тормазит, не знаю с чем это связано, скорее всего так framework написан.
    Проблема 2: Видео крутится ПО КРУГУ. То есть не срабатывает проверка
                    if (c==27) break;
    Более того, когда приглядимся поближе заметим, что функция
                    cvWaitKey(33);
    всегда возвращает -1.
    Проблема 3: Нельзя собрать проект под x64 архитектуру.
    Глубже я не копал, возможно есть еще глюки у этого способа (скорее всего). Так что мне пришлось отказаться от этого способа и искать более изощренный.
    Способ два.
    Описание этого способа можно найти здесь. Вкратце, качаем macports (очень полезный инструмент, много раз выручал). Набираем с командной строке
    sudo port selfupdate
    sudo port install opencv
    не забываем вводить пароль администратора, если потребуется. Ждем, пока macports скомпилят OpenCV и все что для них нужно. На этом установка завершена. Чтобы прикрутить библиотеку к проекту нужно сделать много магических манипуляций.
    Первое: Добавим нужные библиотеки. В меню Project->Add To Project идем в папку
    /opt/local/lib/ (или /usr/local/lib/)
    помечаем там следующие библиотеки
    libcxcore.dylib
    libcvaux.dylib
    libcv.dylib
    libhighgui.dylib
    libml.dylib
    жмем add. Следим, чтобы галочка у “Copy items to destination group`s older(if needed)” была снята. Снова жмем Add.
    Второе: Идем в настройки проекта (помечаем проект, жмем info). Идем на вкладку build. Среди весьма внушительного списка параметров ищем “Header Search Paths” (используйте строку поиска). Вводим туда
    /opt/local/include/opencv/ (или /usr/local/include/opencv/ зависит от того куда встал OpenCV)
    Третье: из графы “Valid Architectures” оставляем только x86_64
    Четвертое(самое неприятное): Проект может собираться только под ТЕКУЩУЮ архитектеру ситемы. То есть, если активен 64 разрядный режим то и собирать надо в 64 разрядном режиме!!! Если 32 битный, собираем в 32 разрядном. Если ошибемся с разрядностью, то будут вылазать левые ошибки в большом количестве, например таких
    Еще один не понятный момент, окошки, которые создает OpenCV почему-то открываются через X11. Зато все отрабатывает без тормазов и правильно.