Le motif de conception Singleton est l’un des plus simple a maîtriser. Mais lorsqu’il s’agit d’accès concurrentiel dans un programme, les implémentations classiques ne suffisent plus. Il faut garder à l’esprit que les architectures multiprocesseurs ont plusieurs caches souvent asynchrones « write-back » par opposition à « write-through » pour synchrone (rare car moins performant).

Un singleton moderne devrait implémenter les notions suivantes :

Qu’est ce qu’un Singleton ? En programmation un Singleton est un objet dont on veut garantir qu’une seule instance vit pendant l’exécution du programme. Imaginez un système d’exploitation qui aurait deux instances de son système de fichier !!! C’est le boxon assuré.

Voici mon implémentation en cplusplus (code source d’exemple) :

//singleton.h
#ifndef __SINGLETON_H_
#define __SINGLETON_H_

#include <cstdlib> //std::atexit()

#define MFENCE			"memory_fence"
#define MUTEX_LOCK		"lock"
#define MUTEX_UNLOCK	"unlock"
#define MEMORY_READWRITE_BARRIER	"memory_barrier"

template <typename T>
class Singleton
{
public:
	static T& instance()
	{
		return *get_instance();
	}

	static const T& const_instance()
	{
		return *get_instance();
	}

	static void destroy()
	{
		MFENCE; //On s'assure que tous les caches processeurs sont à niveau
		if (pInstance_ != 0)
			delete pInstance_;
	}

protected:
	Singleton(){}
	~Singleton()
	{
		pInstance_ = 0;
		created_ = false;
	}

private:
	static T* pInstance_;
	static volatile bool created_;
	Singleton(const Singleton &);
	Singleton& operator= (const Singleton &);

	static T* get_instance()
	{
		if (created_ == false)
		{
			MUTEX_LOCK; //On s'assure qu'un seul thread a la main
			if (pInstance_ == 0)
			{
				pInstance_ = new T();
				std::atexit(Singleton::destroy);
			}
			MUTEX_UNLOCK;
			MEMORY_READWRITE_BARRIER; //On s'assure que le compilateur ne change pas l'ordre de ces 2 instructions
			created_ = true;
			MFENCE; //On s'assure que tous les caches processeurs sont à niveau
		}
		return pInstance_;
	}
};

template<typename T> T* Singleton<T>::pInstance_ = 0;
template<typename T> volatile bool Singleton<T>::created_ = false;

#endif//!__SINGLETON_H_

Notons que pour les mutexes, les meilleures implémentations permettent aussi, en plus d’être dans une zone synchrone, de réaliser l’équilant de [SLM]FENCE (PIII : SFENCE – P4 : LFENCE, MFENCE). Pour ce qui est de l’annulation de l’ordre des instructions par le compilateur, je ne vois rien d’autres que d’utiliser une librairie qui fournit des MACROS adaptés aux différents compilateurs et plateformes.