суббота, 17 февраля 2018 г.

Потокобезопасный Singleton на C#

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

public class Singleton
{
    private static readonly object syncObj = new object();
    private static volatile Singleton instance;

    public Singleton Shared()
    {
        if (instance == null)
        {
            lock (syncObj)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }

    private Singleton()
    {            
    }
    
    // ...
}

Двойная проверка на null нужна для того, чтобы не делать блокировок без необходимости. А если хочется совсем без блокировок? Изучая исходный код пула массивов от майкрософт, нашел реализацию lockfree синглтона. Вот она, очищенная от лишних деталей самого пула:

public class Singleton
{
    private static Singleton instance= null;

    public static Singleton Shared
    {
        get { return Volatile.Read(ref instance) ?? EnsureSharedCreated(); }
    }

    private static Singleton EnsureSharedCreated()
    {
        Interlocked.CompareExchange(ref instance, Create(), null);
        return instance;
    }

    public static Singleton Create()
    {
        return new Singleton();
    }
    
    private Singleton()
    {
    }
    
    // ...
}

пятница, 2 июня 2017 г.

Олимпиадное vs Промышленное программирование

Задачи

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


Время

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

Код

В промышленном программировании код пишется для дальнейшего сопровождения (то есть многократного чтения для расширения и правок). Он должен быть гибким, расширяемым. И самое главное - хорошо читаемым.
В олимпиадном программировании код пишется на один раз (write-only). Никто не собирается его в дальнейшем вычитывать, расширять или править баги.

Навыки

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

Выводы

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

понедельник, 22 мая 2017 г.

FizzBuzz на Python

Программа, печатает числа от 1 до 100, но если число кратно трем вместо числа выводит "Fizz", если число кратно пяти выводит "Buzz". Если число одновременно кратно и трем и пяти - "FizzBuzz".

Python 2
for i in range(1, 101):  # последнее число исключается из цикла
    if i % 15 == 0:
        print 'FizzBuzz'
    elif i % 3 == 0:
        print 'Fizz'
    elif i % 5 == 0:
        print 'Buzz'
    else:
        print i

Если хочется немного оптимизировать, можно заменить range на xrange, тогда не будет создаваться список из исходных чисел. В python 3 range это и есть xrange.

В Python 3 нужно вызывать print как функцию
for i in range(1, 101):
    if i % 15 == 0:
        print('FizzBuzz')
    elif i % 3 == 0:
        print('Fizz')
    elif i % 5 == 0:
        print('Buzz')
    else:
        print(i)

В таком решении дублируется проверка кратности трем и пяти - мы проверяем на кратность 15-ти, то есть трем и пяти одновременно. Также мы используем оператор (или функцию) вывода четыре раза. Этого можно избежать, используя конкатенацию строк.

for i in range(1, 101):
    s = ''
    if i % 3 == 0:
        s = 'Fizz'
    if i % 5 == 0:
        s += 'Buzz'
    if not s:
        s = str(i)
    print s

В таком варианте нет лишней операции получения остатка от деления, но есть проверка на пустоту строки и конкатенация строк.