IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Threads & Processus C++ Discussion :

Comment implémenter correctement std::lock ?


Sujet :

Threads & Processus C++

  1. #1
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut Comment implémenter correctement std::lock ?
    Bonjour,

    Pour rappel, std::lock sert à bloquer plusieurs mutex en évitant les deadlocks.
    Prérequis : documentation : http://en.cppreference.com/w/cpp/thread/lock

    Hier, par curiosité, j'ai regardé l'implémentation de std::lock de GCC 4.9.2, mais j'ai été déçu.
    La voici, accompagnée de l'implémentation de std::try_lock :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
      template<typename _Lock>
        unique_lock<_Lock>
        __try_to_lock(_Lock& __l)
        { return unique_lock<_Lock>(__l, try_to_lock); }
     
      template<int _Idx, bool _Continue = true>
        struct __try_lock_impl
        {
          template<typename... _Lock>
    	static void
    	__do_try_lock(tuple<_Lock&...>& __locks, int& __idx)
    	{
              __idx = _Idx;
              auto __lock = __try_to_lock(std::get<_Idx>(__locks));
              if (__lock.owns_lock())
                {
                  __try_lock_impl<_Idx + 1, _Idx + 2 < sizeof...(_Lock)>::
                    __do_try_lock(__locks, __idx);
                  if (__idx == -1)
                    __lock.release();
                }
    	}
        };
     
      template<int _Idx>
        struct __try_lock_impl<_Idx, false>
        {
          template<typename... _Lock>
    	static void
    	__do_try_lock(tuple<_Lock&...>& __locks, int& __idx)
    	{
              __idx = _Idx;
              auto __lock = __try_to_lock(std::get<_Idx>(__locks));
              if (__lock.owns_lock())
                {
                  __idx = -1;
                  __lock.release();
                }
    	}
        };
     
      /** @brief Generic try_lock.
       *  @param __l1 Meets Mutex requirements (try_lock() may throw).
       *  @param __l2 Meets Mutex requirements (try_lock() may throw).
       *  @param __l3 Meets Mutex requirements (try_lock() may throw).
       *  @return Returns -1 if all try_lock() calls return true. Otherwise returns
       *          a 0-based index corresponding to the argument that returned false.
       *  @post Either all arguments are locked, or none will be.
       *
       *  Sequentially calls try_lock() on each argument.
       */
      template<typename _Lock1, typename _Lock2, typename... _Lock3>
        int
        try_lock(_Lock1& __l1, _Lock2& __l2, _Lock3&... __l3)
        {
          int __idx;
          auto __locks = std::tie(__l1, __l2, __l3...);
          __try_lock_impl<0>::__do_try_lock(__locks, __idx);
          return __idx;
        }
     
      /** @brief Generic lock.
       *  @param __l1 Meets Mutex requirements (try_lock() may throw).
       *  @param __l2 Meets Mutex requirements (try_lock() may throw).
       *  @param __l3 Meets Mutex requirements (try_lock() may throw).
       *  @throw An exception thrown by an argument's lock() or try_lock() member.
       *  @post All arguments are locked.
       *
       *  All arguments are locked via a sequence of calls to lock(), try_lock()
       *  and unlock().  If the call exits via an exception any locks that were
       *  obtained will be released.
       */
      template<typename _L1, typename _L2, typename ..._L3>
        void
        lock(_L1& __l1, _L2& __l2, _L3&... __l3)
        {
          while (true)
            {
              unique_lock<_L1> __first(__l1);
              int __idx;
              auto __locks = std::tie(__l2, __l3...);
              __try_lock_impl<0, sizeof...(_L3)>::__do_try_lock(__locks, __idx);
              if (__idx == -1)
                {
                  __first.release();
                  return;
                }
            }
        }
    Pour que ce soit plus facile à suivre, voici le code équivalent dans le cas où std::lock a 3 paramètres :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    template<class Lockable1, class Lockable2>
    int try_lock(Lockable1& lock1, Lockable2& lock2);
     
    template<class Lockable1, class Lockable2, class Lockable3>
    void lock(Lockable1& lock1, Lockable2& lock2, Lockable3& lock3)
    {
    	while(true) {
    		unique_lock<Lockable1> u1(lock1); // can throw
    		int idx = try_lock(lock2, lock3); // can throw
    		if(idx == -1) {
    			u1.release();
    			return;
    		}
    	}
    }
     
    template<class Lockable1, class Lockable2>
    int try_lock(Lockable1& lock1, Lockable2& lock2)
    {
    	int result = 0;
    	unique_lock<Lockable1> u1(lock1, try_to_lock); // can throw
    	if(u1.owns_lock()) {
    		result = 1;
    		unique_lock<Lockable2> u2(lock2, try_to_lock); // can throw
    		if(u2.owns_lock()) {
    			result = -1;
    			u2.release();
    		}
    		if(result == -1)
    			u1.release();
    	}
    	return result;
    }
    Le problème de cette implémentation est que le premier mutex que la fonction essaie de bloquer est toujours le même, à savoir celui qui est en 1er paramètre.
    Admettons que, par exemple, le 3e mutex soit déjà bloqué et que la fonction essaie de bloquer les 3 mutex. La fonction ne va jamais s'arrêter et va continuellement bloquer et débloquer les deux premiers mutex. Il aurait été préférable qu'elle se rappelle que c'est le 3e mutex qu'elle n'a pas réussi à bloquer puis qu'elle décide, après avoir débloquer les deux premiers mutex, de bloquer le 3e en premier.

    Boost 1.61.0, lui, possèdes des lock non variadics implémentés correctement (je n'ai pas regardé les autres versions de Boost). Voici l'implémentation du boost::lock à 3 paramètres :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    namespace boost
    {
      namespace detail
      {
        // ...
     
        template <typename MutexType1, typename MutexType2, typename MutexType3>
        unsigned lock_helper(MutexType1& m1, MutexType2& m2, MutexType3& m3)
        {
          boost::unique_lock<MutexType1> l1(m1);
          if (unsigned const failed_lock=try_lock_internal(m2,m3))
          {
            return failed_lock;
          }
          l1.release();
          return 0;
        }
     
        // ...
     
      }
     
      // ...
     
      template <typename MutexType1, typename MutexType2, typename MutexType3>
      void lock(MutexType1& m1, MutexType2& m2, MutexType3& m3)
      {
        unsigned const lock_count = 3;
        unsigned lock_first = 0;
        for (;;)
        {
          switch (lock_first)
          {
          case 0:
            lock_first = detail::lock_helper(m1, m2, m3);
            if (!lock_first) return;
            break;
          case 1:
            lock_first = detail::lock_helper(m2, m3, m1);
            if (!lock_first) return;
            lock_first = (lock_first + 1) % lock_count;
            break;
          case 2:
            lock_first = detail::lock_helper(m3, m1, m2);
            if (!lock_first) return;
            lock_first = (lock_first + 2) % lock_count;
            break;
          }
        }
      }
     
      // ...
     
    }
    Mais comment faire une implémentation correcte avec des variadic templates ?
    Aujourd'hui, je viens d'en faire une avec du type erasure :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    class LockableBase
    {
    public:
    	virtual ~LockableBase() {}
    	virtual void lock()     = 0;
    	virtual bool try_lock() = 0;
    	virtual void unlock()   = 0;
    };
     
    template<class Lockable>
    class LockableWrapper : public LockableBase
    {
    private:
    	Lockable& m_lock;
    public:
    	explicit LockableWrapper(Lockable& lock) : m_lock(lock) {}
    	~LockableWrapper() override {}
    	void lock()        override { m_lock.lock();            }
    	bool try_lock()    override { return m_lock.try_lock(); }
    	void unlock()      override { m_lock.unlock();          }
    };
     
    class LockableAny
    {
    private:
    	std::unique_ptr<LockableBase> m_lockBase;
     
    public:
    	LockableAny() {}
     
    	template<class Lockable>
    	LockableAny(Lockable& lock) :
    		m_lockBase(new LockableWrapper<Lockable>(lock))
    	{
    	}
     
    	void lock()     { assert(m_lockBase); m_lockBase->lock();            }
    	bool try_lock() { assert(m_lockBase); return m_lockBase->try_lock(); }
    	void unlock()   { assert(m_lockBase); m_lockBase->unlock();          }
    };
     
    template<size_t CurrentIndex, class LockableAnyArrayType, class Lockable>
    void fillLockableAnyArray(LockableAnyArrayType& arrayLocks, Lockable& lock)
    {
    	arrayLocks[CurrentIndex] = LockableAny(lock);
    }
     
    template<size_t CurrentIndex, class LockableAnyArrayType, class Lockable1, class ...LockableTypes>
    void fillLockableAnyArray(LockableAnyArrayType& arrayLocks, Lockable1& lock1, LockableTypes&... lockN)
    {
    	arrayLocks[CurrentIndex] = LockableAny(lock1);
    	fillLockableAnyArray<CurrentIndex + 1>(arrayLocks, lockN...);
    }
     
    template<class Lockable1, class Lockable2, class... LockableN>
    void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockN)
    {
    	constexpr size_t nbArgs = 2 + sizeof...(LockableN);
     
    	std::array<LockableAny, nbArgs> arrayLocks;
    	fillLockableAnyArray<0>(arrayLocks, lock1, lock2, lockN...);
     
    //	std::array<unique_lock<LockableAny>, nbArgs> arrayUniqueLocks;
    		// EDIT 18/09/2016 vers 2h34 : bogue corrigé : ligne déplacée vers l'intérieur de la boucle while
     
    	size_t firstLock = 0;
     
    	while(true)	{
    		std::array<unique_lock<LockableAny>, nbArgs> arrayUniqueLocks;
    		arrayUniqueLocks[firstLock] = unique_lock<LockableAny>(arrayLocks[firstLock]);
    		size_t lockIndex = (firstLock + 1) % nbArgs;
    		bool lockSuccess = true;
    		for(; lockIndex != firstLock && lockSuccess; lockIndex = (lockIndex+1)%nbArgs) {
    			arrayUniqueLocks[lockIndex] = unique_lock<LockableAny>(arrayLocks[lockIndex], try_to_lock);
    			lockSuccess = arrayUniqueLocks[lockIndex].owns_lock();
    		}
    		if(lockSuccess) {
    			for(auto& uLock : arrayUniqueLocks)
    				uLock.release();
    			return;
    		} else {
    			firstLock = (lockIndex + nbArgs - 1) % nbArgs;
    		}
    	}
    }
    Mais ma première implémentation a un inconvénient :
    Je stocke les objets LockableWrapper dans la mémoire dynamique, ce qui n'est pas performant.

    J'ai voulu les mettre dans un std::tuple, mais je n'ai pas réussi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template<class Lockable1, class Lockable2, class... LockableN>
    void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockN)
    {
    	std::tuple<
    		LockableWrapper<Lockable1>,
    		LockableWrapper<Lockable2>,
    		LockableWrapper<LockableN>
    	> locks(
    		LockableWrapper<Lockable1>(lock1),
    		LockableWrapper<Lockable2>(lock2),
    		LockableWrapper<LockableN>(lockN...) // Ne compile pas.
    	);
    	// ...
    }
    Finalement, dans ma deuxième implémentation, j'ai triché en sauvegardant le LockableWrapper directement dans LockableAny. Seule la classe LockableAny a changé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class LockableAny
    {
    private:
    	std::aligned_storage<sizeof (LockableWrapper<std::mutex>),
    	                     alignof(LockableWrapper<std::mutex>)>::type m_data;
     
    	LockableBase& getLockBaseRef()
    	{
    		return reinterpret_cast<LockableBase&>(m_data);
    	}
     
    public:
    	LockableAny() {}
     
    	template<class Lockable>
    	LockableAny(Lockable& lock)
    	{
    		static_assert(   sizeof (LockableWrapper<Lockable>) == sizeof (LockableWrapper<std::mutex>)
    		              && alignof(LockableWrapper<Lockable>) == alignof(LockableWrapper<std::mutex>),
    		              "Hack échoué. Tous les LockableWrapper doivent avoir la même taille et le même alignement."
    		              " Sinon, changer le type de m_data en std::unique_ptr<LockableBase> et adapter le code.");
     
    		new(&m_data) LockableWrapper<Lockable>(lock);
    	}
     
    	void lock()     { getLockBaseRef().lock();            }
    	bool try_lock() { return getLockBaseRef().try_lock(); }
    	void unlock()   { getLockBaseRef().unlock();          }
    };
    Les LockableWrapper ont tous la même taille et le même alignement, car ils contiennent tous la même chose : un pointeur vers une table virtuelle et une référence.
    Le destructeur de LockableWrapper n'est pas appelé, mais ce n'est pas grave : il ne fait rien.
    Cependant, c'est assez moche et je crains que ce ne soit pas portable à cause du reinterpret_cast.

    En tout cas, je n'ai pas trouvé comment implémenter correctement std::lock sans passer par des fonctions virtuelles ou des pointeurs de fonction.
    Est-ce possible ? Si oui, est-ce que l'un d'entre vous y arriverait ? Le défi est lancé !

  2. #2
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Pour faire comme boost, il faut crée chaque cas du switch et l'associer au numéro du mutex correspondant. Un truc comme ci-dessous (pas essayé de compiler).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    template<typename Lockable>
    std::unique_lock<Lockable>
    try_to_lock(Lockable & lock)
    {
      return std::unique_lock<Lockable>(lock, std::try_to_lock);
    }
     
    template<std::size_t I, class LockableTuple>
    std::size_t
    try_lock_impl(std::integer_sequence<std::size_t, I>, LockableTuple const & t)
    {
      auto ulock = try_to_lock(std::get<I>(t));
      if (ulock.owns_lock()) {
        ulock.release();
        return std::tuple_size<LockableTuple>::value;
      }
      return I;
    }
     
    template<std::size_t I, std::size_t... Ints, class LockableTuple>
    std::size_t
    try_lock_impl(std::integer_sequence<std::size_t, I, Ints...>, LockableTuple const & t)
    {
      auto ulock = try_to_lock(std::get<I>(t));
      if (ulock.owns_lock()) {
        auto id = try_lock_impl(std::integer_sequence<std::size_t, Ints...>{}, t);
        if (id == std::tuple_size<LockableTuple>::value) {
          ulock.release();
        }
        return id;
      }
      return I;
    }
     
    template<std::size_t First, std::size_t Int, std::size_t... Ints, class LockableTuple>
    std::size_t
    lock_impl_from(std::integer_sequence<std::size_t, Int, Ints...>, LockableTuple const & t)
    {
      auto & lock = u1(std::get<First>(t));
      std::unique_lock<std::remove_reference_t<decltype(lock)>> ulock(lock);
      auto id = try_lock_impl(std::integer_sequence<std::size_t, (Ints + First) % sizeof...(Ints) ...>{}, t);
      if (id == std::tuple_size<LockableTuple>::value) {
        ulock.release();
      }
      return id;
    }
     
    template<std::size_t... Ints, class LockableTuple>
    void
    lock_impl(std::integer_sequence<std::size_t, Ints...> ints, LockableTuple const & t)
    {
      std::size_t const lock_count = sizeof...(Ints);
      std::size_t lock_first = 0;
      for (;;) {
        bool has_result = false
        // une espèce de switch pas optimisé
        (void)std::initializer_list<int>{(
          (not has_result && Ints == lock_first) ? void(lock_first = lock_impl_from<Ints>(ints, t), has_result = 1) : void()
        )..., 1)};
        if (lock_first == sizeof...(Ints)) {
          return;
        }
      }
    }
     
    template<class Lockable1, class Lockable2, class... Lockables>
    void
    lock(Lockable1 & lock1, Lockable2 & lock2, Lockables &... locks)
    {
      lock_impl(std::index_sequence<sizeof...(Lockables)+2>{}, std::forward_as_tuple(lock1, lock2, locks...));
    }

  3. #3
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Dans ton lock_impl, j'ai ajouté l'accolade fermante manquante de la boucle for, mais ça ne compile toujours pas, même avec g++ 6.1.0.
    Voici le message d'erreur en commentaires, dans le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    template<std::size_t... Ints, class LockableTuple>
    void
    lock_impl(std::integer_sequence<std::size_t, Ints...> ints, LockableTuple const & t)
    {
      std::size_t const lock_count = sizeof...(Ints);
      std::size_t lock_first = 0;
      for (;;) {
        // une espèce de switch pas optimisé
        (void)std::initializer_list<int>{
          (Ints == lock_first ? void(lock_first = try_lock<Ints>(ints, t)) : void())...
            // main.cpp:62:5: error: no matching function for call to
            // 'std::initializer_list<int>::initializer_list(<brace-enclosed initializer list>)'
            // (void)std::initializer_list<int>{
            // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            //   (Ints == lock_first ? void(lock_first = try_lock<Ints>(ints, t)) : void())...
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            // };
            // ~
        };
        if (lock_first == sizeof...(Ints)) {
          return;
        }
      } //////////////////// accolade fermante ajoutée
    }
    Je viens de regarder la doc des constructeurs de std::initializer_list, mais il n'y a que des constructeurs sans paramètre, d'où l'erreur de compilation :
    http://en.cppreference.com/w/cpp/uti...itializer_list

    A part ça, j'ai analysé ton code, mais il y a un problème : tu fais un try_lock à la place d'un lock sur le premier mutex à bloquer. En fait, tu n'appelles jamais lock.
    Normalement, il faut faire un lock sur un mutex puis un try_lock sur tous les autres.

    Émuler le switch pour faire la même chose que dans Boost mais avec la contrainte du variadic template, c'est la partie qui me semble la plus compliquée.

  4. #4
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    En parlant d'erreurs, je me suis trompé dans mon implémentation de std::lock : j'ai déclaré mon std::array<std::unique_lock<LockableAny>, nbArgs> hors de la boucle while, au lieu de à l'intérieur. Du coup, les unlock ne sont pas appelés en cas d'échec de la première itération.

    Code corrigé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    #include <cassert>
    #include <memory>
    #include <mutex>
     
    namespace pyramidev
    {
     
    class LockableBase
    {
    public:
    	virtual ~LockableBase() {}
    	virtual void lock()     = 0;
    	virtual bool try_lock() = 0;
    	virtual void unlock()   = 0;
    };
     
    template<class Lockable>
    class LockableWrapper : public LockableBase
    {
    private:
    	Lockable& m_lock;
    public:
    	explicit LockableWrapper(Lockable& lock) : m_lock(lock) {}
    	~LockableWrapper() override {}
    	void lock()        override { m_lock.lock();            }
    	bool try_lock()    override { return m_lock.try_lock(); }
    	void unlock()      override { m_lock.unlock();          }
    };
     
    class LockableAny
    {
    private:
    	std::unique_ptr<LockableBase> m_lockBase;
     
    public:
    	LockableAny() {}
     
    	template<class Lockable>
    	LockableAny(Lockable& lock) :
    		m_lockBase(new LockableWrapper<Lockable>(lock))
    	{
    	}
     
    	void lock()     { assert(m_lockBase); m_lockBase->lock();            }
    	bool try_lock() { assert(m_lockBase); return m_lockBase->try_lock(); }
    	void unlock()   { assert(m_lockBase); m_lockBase->unlock();          }
    };
     
    template<size_t CurrentIndex, class LockableAnyArrayType, class Lockable>
    void fillLockableAnyArray(LockableAnyArrayType& arrayLocks, Lockable& lock)
    {
    	arrayLocks[CurrentIndex] = LockableAny(lock);
    }
     
    template<size_t CurrentIndex, class LockableAnyArrayType, class Lockable1, class ...LockableTypes>
    void fillLockableAnyArray(LockableAnyArrayType& arrayLocks, Lockable1& lock1, LockableTypes&... lockN)
    {
    	arrayLocks[CurrentIndex] = LockableAny(lock1);
    	fillLockableAnyArray<CurrentIndex + 1>(arrayLocks, lockN...);
    }
     
    template<class Lockable1, class Lockable2, class... LockableN>
    void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockN)
    {
    	constexpr size_t nbArgs = 2 + sizeof...(LockableN);
     
    	std::array<LockableAny, nbArgs> arrayLocks;
    	fillLockableAnyArray<0>(arrayLocks, lock1, lock2, lockN...);
     
    	size_t firstLock = 0;
     
    	while(true) {
    		std::array<std::unique_lock<LockableAny>, nbArgs> arrayUniqueLocks;
    		arrayUniqueLocks[firstLock] = std::unique_lock<LockableAny>(arrayLocks[firstLock]);
    		size_t lockIndex = (firstLock + 1) % nbArgs;
    		bool lockSuccess = true;
    		for(; lockIndex != firstLock && lockSuccess; lockIndex = (lockIndex+1)%nbArgs) {
    			arrayUniqueLocks[lockIndex] = std::unique_lock<LockableAny>(arrayLocks[lockIndex], std::try_to_lock);
    			lockSuccess = arrayUniqueLocks[lockIndex].owns_lock();
    		}
    		if(lockSuccess) {
    			for(auto& uLock : arrayUniqueLocks)
    				uLock.release();
    			return;
    		}
    		firstLock = (lockIndex + nbArgs - 1) % nbArgs;
    	}
    }
     
    } // namespace pyramidev

  5. #5
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    J'ai oublié de mettre une valeur dans l'initializer_list, normalement cela ressemble à std::initializer_list<int>{(void(des trucs), 1)...};.

    C'est efficace pour appliquer une fonction sur tous les éléments d'une variadique, mais moins pour simuler un switch justement parce que la condition s'applique sur tous les éléments (ce qui oblige l'utilisation d'un booléan de contrôle).
    Faire un switch efficace est possible, mais plutôt moche:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    template<class T, class I, class TupleFunc>
    std::enable_if_t<(I::value + 255 >= std::tuple_size<TupleFunc>::value)>
    cswitch_impl(I, T v, TupleFunc const & t)
    {
      switch(v) {
        case 0: std::get<0>(t)(/*int_<I::value + 0>{}*/); break;
        case 1: std::get<1>(t)(/*int_<I::value + 1>{}*/); break;
        //...
        case 255: std::get<255>(t)(/*int_<I::value + 255>{}*/); break;
    }
     
    template<class T, class I, class TupleFunc>
    std::enable_if_t<(I::value + 255 < std::tuple_size<TupleFunc>::value)>
    cswitch_impl(I, T v, TupleFunc const & t)
    {
      switch(v) {
        case 0: std::get<0>(t)(/*int_<I::value + 0>{}*/); break;
        case 1: std::get<1>(t)(/*int_<I::value + 1>{}*/); break;
        //...
        case 254: std::get<254>(t)(/*int_<I::value + 254>{}*/); break;
        case 255: cswitch(int_<I::value + 255>{}, v - 255, t); break;
    }
     
    template<class F0, std::size_t Ints..., class... F>
    auto cswitch_tuple(F0, std::integer_sequence<std::size_t, Ints...> F & ... f)
    {
      return std::forward_as_tuple(f, (Ints, F0)...);
    }
     
    template<class T, class F>
    void cswitch(T v, F && ... f) 
    {
      cswitch_impl(int_<0>{}, v, cswitch_tuple([](/*auto*/){}, std::index_sequence<254 - ((sizeof...(f) +254) % 255)>{}, f...));
    }
    Concernant le lock/try_lock, j'ai effectivement raté la première étape, je fais la modif.

  6. #6
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Après que j'ai fait trois petits changements dans ton lock_impl, tes templates passent la première phase de compilation, mais ça ne compile plus quand j'essaie de les instancier.
    Dans le code, j'ai ajouté en commentaire le 1er avertissement et la 1re erreur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    // Code compilé ici : http://coliru.stacked-crooked.com/
    // Version de g++ : 6.1.0
    // Commande : g++ -std=c++14 -O2 -Wall -pedantic -pthread -fpermissive main.cpp && ./a.out
     
    #include <iostream>
    #include <mutex>
    #include <tuple>
    #include <utility>
     
    namespace jo_link_noir
    {
     
    template<typename Lockable>
    std::unique_lock<Lockable>
    try_to_lock(Lockable & lock)
    {
      return std::unique_lock<Lockable>(lock, std::try_to_lock);
    }
     
    template<std::size_t I, class LockableTuple>
    std::size_t
    try_lock_impl(std::integer_sequence<std::size_t, I>, LockableTuple const & t)
    {
      auto ulock = try_to_lock(std::get<I>(t));
      if (ulock.owns_lock()) {
        ulock.release();
        return std::tuple_size<LockableTuple>::value;
      }
      return I;
    }
     
    template<std::size_t I, std::size_t... Ints, class LockableTuple>
    std::size_t
    try_lock_impl(std::integer_sequence<std::size_t, I, Ints...>, LockableTuple const & t)
    {
      auto ulock = try_to_lock(std::get<I>(t));
      if (ulock.owns_lock()) {
        auto id = try_lock_impl(std::integer_sequence<std::size_t, Ints...>{}, t);
        if (id == std::tuple_size<LockableTuple>::value) {
          ulock.release();
        }
        return id;
      }
      return I;
    }
     
    template<std::size_t First, std::size_t Int, std::size_t... Ints, class LockableTuple>
    std::size_t
    lock_impl_from(std::integer_sequence<std::size_t, Int, Ints...>, LockableTuple const & t)
    {
      auto & lock = u1(std::get<First>(t));
      // /usr/local/include/c++/6.1.0/tuple: In instantiation of 'class std::tuple_element<1ul, std::tuple<std::mutex&> >':
      // /usr/local/include/c++/6.1.0/tuple:1205:12:   recursively required from 'class std::tuple_element<2ul, std::tuple<std::recursive_mutex&, std::mutex&> >'
      // /usr/local/include/c++/6.1.0/tuple:1205:12:   required from 'class std::tuple_element<3ul, std::tuple<std::mutex&, std::recursive_mutex&, std::mutex&> >'
      // /usr/local/include/c++/6.1.0/utility:106:69:   required by substitution of 'template<long unsigned int __i, class _Tp> using __tuple_element_t = typename std::tuple_element::type [with long unsigned int __i = 3ul; _Tp = std::tuple<std::mutex&, std::recursive_mutex&, std::mutex&>]'
      // /usr/local/include/c++/6.1.0/tuple:1241:5:   required by substitution of 'template<long unsigned int __i, class ... _Elements> constexpr std::__tuple_element_t<__i, std::tuple<_Elements ...> >& std::get(const std::tuple<_Elements ...>&) [with long unsigned int __i = 3ul; _Elements = {std::mutex&, std::recursive_mutex&, std::mutex&}]'
      // main.cpp:52:35:   required from 'std::size_t jo_link_noir::lock_impl_from(std::integer_sequence<long unsigned int, Int, Ints ...>, const LockableTuple&) [with long unsigned int First = 3ul; long unsigned int Int = 3ul; long unsigned int ...Ints = {}; LockableTuple = std::tuple<std::mutex&, std::recursive_mutex&, std::mutex&>; std::size_t = long unsigned int]'
      // main.cpp:71:86:   required from 'void jo_link_noir::lock_impl(std::integer_sequence<long unsigned int, _Idx ...>, const LockableTuple&) [with long unsigned int ...Ints = {3ul}; LockableTuple = std::tuple<std::mutex&, std::recursive_mutex&, std::mutex&>]'
      // main.cpp:86:12:   required from 'void jo_link_noir::lock(Lockable1&, Lockable2&, Lockables& ...) [with Lockable1 = std::mutex; Lockable2 = std::recursive_mutex; Lockables = {std::mutex}]'
      // main.cpp:96:31:   required from here
      // /usr/local/include/c++/6.1.0/tuple:1205:12: error: invalid use of incomplete type 'class std::tuple_element<0ul, std::tuple<> >'
      //      struct tuple_element<__i, tuple<_Head, _Tail...> >
      //            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      // In file included from /usr/local/include/c++/6.1.0/tuple:38:0,
      //                  from /usr/local/include/c++/6.1.0/mutex:38,
      //                  from main.cpp:7:
      // /usr/local/include/c++/6.1.0/utility:102:12: note: declaration of 'class std::tuple_element<0ul, std::tuple<> >'
      //      struct tuple_element;
      //             ^~~~~~~~~~~~~
      std::unique_lock<std::remove_reference_t<decltype(lock)>> ulock(lock);
      auto id = try_lock_impl(std::integer_sequence<std::size_t, (Ints + First) % sizeof...(Ints) ...>{}, t);
      if (id == std::tuple_size<LockableTuple>::value) {
        ulock.release();
      }
      return id;
    }
     
    template<std::size_t... Ints, class LockableTuple>
    void
    lock_impl(std::integer_sequence<std::size_t, Ints...> ints, LockableTuple const & t)
    {
      std::size_t const lock_count = sizeof...(Ints);
      std::size_t lock_first = 0;
      for (;;) {
        bool has_result = false; //////////////////// ajout de ";"
        // une espèce de switch pas optimisé
        (void)std::initializer_list<int>{(( //////////////////// ajout d'une 2e parenthèse
          (not has_result && Ints == lock_first) ? void(lock_first = lock_impl_from<Ints>(ints, t), has_result = 1) : void()
        ), 1)...}; //////////////////// déplacement du "..."
        // main.cpp:71:48: warning: expression list treated as compound expression in functional cast [-fpermissive]
        // (not has_result && Ints == lock_first) ? void(lock_first = lock_impl_from<Ints>(ints, t), has_result = 1) : void()
        //                                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        if (lock_first == sizeof...(Ints)) {
          return;
        }
      }
    }
     
    template<class Lockable1, class Lockable2, class... Lockables>
    void
    lock(Lockable1 & lock1, Lockable2 & lock2, Lockables &... locks)
    {
      lock_impl(std::index_sequence<sizeof...(Lockables)+2>{}, std::forward_as_tuple(lock1, lock2, locks...));
    }
     
    } // namespace jo_link_noir
     
    int main()
    {
    	std::cout << "Version de g++ : " << __VERSION__ << "\n\n";
    	std::mutex m1, m3;
    	std::recursive_mutex m2;
    	jo_link_noir::lock(m1, m2, m3);
    	m1.unlock();
    	m2.unlock();
    	m3.unlock();
    	return 0;
    }

  7. #7
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Voici la version corrigée avec en commentaire les corrections. Il y avait quand même de grosses boulettes.

    (clang++-3.8, g++-5.4, g++-6.0)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
     
    #include <iostream>
    #include <mutex>
    #include <tuple>
    #include <utility>
     
    namespace jo_link_noir
    {
     
    template<typename Lockable>
    std::unique_lock<Lockable>
    try_to_lock(Lockable & lock)
    {
      return std::unique_lock<Lockable>(lock, std::try_to_lock);
    }
     
    template<std::size_t I, class LockableTuple>
    std::size_t
    try_lock_impl(std::integer_sequence<std::size_t, I>, LockableTuple const & t)
    {
      auto ulock = try_to_lock(std::get<I>(t));
      if (ulock.owns_lock()) {
        ulock.release();
        return std::tuple_size<LockableTuple>::value;
      }
      return I;
    }
     
    // I1 pour contourner l'appel ambigu dans clang (bug ?)
    template<std::size_t I0, std::size_t I1, std::size_t... Ints, class LockableTuple>
    std::size_t
    try_lock_impl(std::integer_sequence<std::size_t, I0, I1, Ints...>, LockableTuple const & t)
    {
      auto ulock = try_to_lock(std::get<I0>(t));
      if (ulock.owns_lock()) {
        auto id = try_lock_impl(std::integer_sequence<std::size_t, I1, Ints...>{}, t);
        if (id == std::tuple_size<LockableTuple>::value) {
          ulock.release();
        }
        return id;
      }
      return I0;
    }
     
    template<std::size_t First, std::size_t Int, std::size_t... Ints, class LockableTuple>
    std::size_t
    lock_impl_from(std::integer_sequence<std::size_t, Int, Ints...>, LockableTuple const & t)
    {
      // suppression de l'instanciation de u1 (Oo)
      auto & lock = std::get<First>(t);
      constexpr std::size_t count_lock = std::tuple_size<LockableTuple>::value;
      std::unique_lock<std::remove_reference_t<decltype(lock)>> ulock(lock);
      // count_lock à la place de sizeof...(Ints).  Surtout qu'il manque Int qui n'est pas dans Ints
      auto id = try_lock_impl(std::integer_sequence<std::size_t, (Ints + First) % count_lock ...>{}, t);
      if (id == count_lock) {
        ulock.release();
      }
      return id;
    }
     
    template<std::size_t... Ints, class LockableTuple>
    void
    lock_impl(std::integer_sequence<std::size_t, Ints...> ints, LockableTuple const & t)
    {
      std::size_t lock_first = 0;
      for (;;) {
        bool has_result = false;
        // une espèce de switch pas optimisé
        (void)std::initializer_list<int>{((
          // double parenthésage dans le void (parce que void ne prend qu'un paramètre...)
          (not has_result && Ints == lock_first) ? void((lock_first = lock_impl_from<Ints>(ints, t), has_result = 1)) : void()
        ), 1)...};
        if (lock_first == sizeof...(Ints)) {
          return;
        }
      }
    }
     
    template<class Lockable1, class Lockable2, class... Lockables>
    void
    lock(Lockable1 & lock1, Lockable2 & lock2, Lockables &... locks)
    {
      //make_index_sequence à la place index_sequence
      lock_impl(std::make_index_sequence<sizeof...(locks)+2>{}, std::forward_as_tuple(lock1, lock2, locks...));
    }
     
    } // namespace jo_link_noir
     
    int main()
    {
    	std::cout << "Version de g++ : " << __VERSION__ << "\n\n";
    	std::mutex m1, m3;
    	std::recursive_mutex m2;
    	jo_link_noir::lock(m1, m2, m3);
    	m1.unlock();
    	m2.unlock();
    	m3.unlock();
    	return 0;
    }

  8. #8
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Test :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    struct FunnyMutexA
    {
        void lock()     {std::cout << "FunnyMutexA::lock()\n";}
        bool try_lock() {std::cout << "FunnyMutexA::try_lock()\n"; return true;}
        void unlock()   {std::cout << "FunnyMutexA::unlock()\n";}
    };
     
     
    struct FunnyMutexB
    {
        void lock()     {std::cout << "FunnyMutexB::lock()\n";}
        bool try_lock() {std::cout << "FunnyMutexB::try_lock()\n"; return true;}
        void unlock()   {std::cout << "FunnyMutexB::unlock()\n";}
    };
     
     
    struct FunnyMutexC
    {
        void lock()     {std::cout << "FunnyMutexC::lock()\n";}
        bool try_lock() {std::cout << "FunnyMutexC::try_lock()\n"; return false;}
        void unlock()   {std::cout << "FunnyMutexC::unlock()\n";}
    };
     
     
    int main()
    {
        std::cout << "Version de g++ : " << __VERSION__ << "\n\n";
        FunnyMutexA mA;
        FunnyMutexB mB;
        FunnyMutexC mC;
        std::cout << "jo_link_noir::lock(mA, mB, mC) : début.\n";
        jo_link_noir::lock(mA, mB, mC);
        std::cout << "jo_link_noir::lock(mA, mB, mC) : fin.\n";
        return 0;
    }
    Sortie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Version de g++ : 6.1.0
     
    jo_link_noir::lock(mA, mB, mC) : début.
    FunnyMutexA::lock()
    FunnyMutexB::try_lock()
    FunnyMutexC::try_lock()
    FunnyMutexB::unlock()
    FunnyMutexA::unlock()
    FunnyMutexC::lock()
    FunnyMutexA::try_lock()
    FunnyMutexB::try_lock()
    jo_link_noir::lock(mA, mB, mC) : fin.
    Test réussi.

    Félicitations ! Tu as réussi le défi !

Discussions similaires

  1. [Multithreading] Comment l'implémenter correctement ?
    Par mrrenard dans le forum Windows Presentation Foundation
    Réponses: 5
    Dernier message: 25/09/2008, 23h08
  2. Comment implémenter lemonldap?
    Par Aldo dans le forum Apache
    Réponses: 7
    Dernier message: 25/01/2007, 21h32
  3. Réponses: 2
    Dernier message: 23/07/2006, 15h07
  4. Comment implémenter un Datawarehouse ?
    Par raslain dans le forum Alimentation
    Réponses: 2
    Dernier message: 20/10/2005, 11h09
  5. Comment utiliser correctement le debugger de DevCPP
    Par Le Furet dans le forum Dev-C++
    Réponses: 2
    Dernier message: 29/09/2005, 09h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo