От време на време всеки ползва Singleton като (анти) шаблон за дизайн.
От време на време пишем и многонишкови програми.
От време на време ползваме тези двете в една програма.
Сложното тогава е, как да сме сигурни че ще имаме точно една инстанция на обекта, който желаем.
Нишките трябва да се разберат една да създаде инстанцията, а другите да не и се пречкат.
Наивният начин за имплементиране на това е с обикновен lock :
1 2 3 4 5 6 |
Singleton& get() { GuardedLock guardedLock(lock);//(1) if (!instance) instance = new Singleton; //(2) return *instance; } |
Така нещата ще потръгнат. Проблема е, че в (1) ще имаме lock-ване всеки път, когато викаме get(). Това може да е много често. А всъщност имаме нужда от 1 локване – само при създаването на обекта (тоест в (2)).
От тази книга навремето научих за double-checking метода. В общи линии, идеята е проста.
1 2 3 4 5 6 7 8 |
Singleton& get() { if (!instance) {//(1) GuardedLock guardedLock(lock);//(2) if (!instance) instance = new Singleton; } return *instance; } |
Вместо да локваме всеки път, локваме само ако няма instance (1). След това локваме и проверяваме пак (2) – така хем имаме синхронизация, хем почти няма локване.
Като начин това работи *почти* винаги, и заради това е известен anti-pattern.
Без да е volatile указателя, компилатора(или интерпретатора) могат да спретнат кофти номер. Въпреки че логически изглежда абсолютно коректен, тов не е съвсем така. Както в С++98, така и в Java, така и навярно в други езици. Заради разни полу-странни оптимизации описани тук.
Ако указателя е volatile, тогава всичко се случва както се очаква.
Той обаче може да се ползва и на други места из кода – това че е volatile може да причини overhead именно там – онези полу-странни оптимизации ще бъдат блокирани и на места, на които не е нужно.
А и нещата с тази проста идея почнаха да загрубяват.
Lock, volatile, double-checking, overheads.
Сега, по темата. Със С++11 освен unique_ptr и move constructors получаваме още няколко неща. Ето една извадка от стандарта.
1 2 3 |
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.—§6.7 [stmt.dcl] p4 |
Shall wait for completion of the initialization.
Така, с новият стандарт, всичко казано дотук може да бъде заменено със следното.
1 2 3 4 |
static Singleton& get(){ static Singleton instance; return instance; } |
Програмата самичка ще се грижи нишките да се изчакат правилно.
А с решаването на всички възможни проблеми се заемат хората, пишещи компилатори :).
п.с.
В новият стандарт се намира и това, което също може да реши проблема сравнително елегантно, с неизвестено какъв overhead.
Anyway, pretty cool stuff.
Още едно интересно четиво – http://goo.gl/lz6gpn. Не използвайте singleton…