Comment déclarer un vecteur de atomique en C++
J'ai l'intention de déclarer un vecteur de atomique variables à être utilisés comme des pions dans un programme multithread. Voici ce que j'ai essayé:
#include <atomic>
#include <vector>
int main(void)
{
std::vector<std::atomic<int>> v_a;
std::atomic<int> a_i(1);
v_a.push_back(a_i);
return 0;
}
Et c'est le fâcheusement détaillé message d'erreur de gcc 4.6.3:
In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0,
from /usr/include/c++/4.6/bits/allocator.h:48,
from /usr/include/c++/4.6/vector:62,
from test_atomic_vec.h:2,
from test_atomic_vec.cc:1:
/usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = std::atomic<int>, __gnu_cxx::new_allocator<_Tp>::pointer = std::atomic<int>*]’:
/usr/include/c++/4.6/bits/stl_vector.h:830:6: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’
test_atomic_vec.cc:10:20: instantiated from here
/usr/include/c++/4.6/ext/new_allocator.h:108:9: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’
/usr/include/c++/4.6/atomic:538:7: error: declared here
In file included from /usr/include/c++/4.6/vector:70:0,
from test_atomic_vec.h:2,
from test_atomic_vec.cc:1:
/usr/include/c++/4.6/bits/vector.tcc: In member function ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’:
/usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’
test_atomic_vec.cc:10:20: instantiated from here
/usr/include/c++/4.6/bits/vector.tcc:319:4: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’
/usr/include/c++/4.6/atomic:538:7: error: declared here
/usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’
test_atomic_vec.cc:10:20: instantiated from here
/usr/include/c++/4.6/bits/vector.tcc:319:4: error: use of deleted function ‘std::atomic<int>& std::atomic<int>::operator=(const std::atomic<int>&)’
/usr/include/c++/4.6/atomic:539:15: error: declared here
In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0,
from /usr/include/c++/4.6/bits/allocator.h:48,
from /usr/include/c++/4.6/vector:62,
from test_atomic_vec.h:2,
from test_atomic_vec.cc:1:
/usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, _Args&& ...) [with _Args = {std::atomic<int>}, _Tp = std::atomic<int>, __gnu_cxx::new_allocator<_Tp>::pointer = std::atomic<int>*]’:
/usr/include/c++/4.6/bits/vector.tcc:306:4: instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’
/usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’
test_atomic_vec.cc:10:20: instantiated from here
/usr/include/c++/4.6/ext/new_allocator.h:114:4: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’
/usr/include/c++/4.6/atomic:538:7: error: declared here
In file included from /usr/include/c++/4.6/vector:61:0,
from test_atomic_vec.h:2,
from test_atomic_vec.cc:1:
/usr/include/c++/4.6/bits/stl_algobase.h: In static member function ‘static _BI2 std::__copy_move_backward<true, false, std::random_access_iterator_tag>::__copy_move_b(_BI1, _BI1, _BI2) [with _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’:
/usr/include/c++/4.6/bits/stl_algobase.h:581:18: instantiated from ‘_BI2 std::__copy_move_backward_a(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’
/usr/include/c++/4.6/bits/stl_algobase.h:590:34: instantiated from ‘_BI2 std::__copy_move_backward_a2(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’
/usr/include/c++/4.6/bits/stl_algobase.h:661:15: instantiated from ‘_BI2 std::move_backward(_BI1, _BI1, _BI2) [with _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’
/usr/include/c++/4.6/bits/vector.tcc:313:4: instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’
/usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’
test_atomic_vec.cc:10:20: instantiated from here
/usr/include/c++/4.6/bits/stl_algobase.h:546:6: error: use of deleted function ‘std::atomic<int>& std::atomic<int>::operator=(const std::atomic<int>&)’
/usr/include/c++/4.6/atomic:539:15: error: declared here
In file included from /usr/include/c++/4.6/vector:63:0,
from test_atomic_vec.h:2,
from test_atomic_vec.cc:1:
/usr/include/c++/4.6/bits/stl_construct.h: In function ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = std::atomic<int>, _Args = {std::atomic<int>}]’:
/usr/include/c++/4.6/bits/stl_uninitialized.h:77:3: instantiated from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*, bool _TrivialValueTypes = false]’
/usr/include/c++/4.6/bits/stl_uninitialized.h:119:41: instantiated from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*]’
/usr/include/c++/4.6/bits/stl_uninitialized.h:259:63: instantiated from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*, _Tp = std::atomic<int>]’
/usr/include/c++/4.6/bits/stl_uninitialized.h:269:24: instantiated from ‘_ForwardIterator std::__uninitialized_move_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = std::atomic<int>*, _ForwardIterator = std::atomic<int>*, _Allocator = std::allocator<std::atomic<int> >]’
/usr/include/c++/4.6/bits/vector.tcc:343:8: instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’
/usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’
test_atomic_vec.cc:10:20: instantiated from here
/usr/include/c++/4.6/bits/stl_construct.h:76:7: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’
/usr/include/c++/4.6/atomic:538:7: error: declared here
Comment puis-je résoudre ce problème?
L'erreur disparaît lorsque j'en commentaire la ligne avec push_back()
.
edit: j'ai édité le post... Pour ceux d'entre vous qui ont vu le premier post, l'erreur était embarrassant que j'ai utilisé gcc
au lieu de g++
:\
- avez-vous essayé d'ajouter -lstdc++ drapeau?
- Rien à voir avec atomics, à tous.
- correct... Mais le problème est survenu dans le code réel avec
g++
juste mon exemple minimal bêtement compilé avecgcc
. Voir modifier. - Pas vraiment en double, mais une pointe dans la bonne direction. Je vous remercie.
- Montre pourquoi c'est une honte, ils n'apportent pas dans les concepts. Peut-ont généré un beaucoup plus propre message d'erreur.
- Clôture comme un double de la stackoverflow.com/questions/12003024/..., permettrait au moins de faire un certain sens, mais la fermeture comme un double d'une question sur les symboles non définis, est un non-sens complet. Le vote pour la rouvrir.
- btw un vecteur (ou aray) de atomics peut probablement conduire à des faux partage ( drdobbs.com/parallel/eliminate-false-sharing/217500206 ). il est généralement préférable de répartir atomics plus faiblement dans la mémoire
- le problème est que l'OP a complètement changé la question entre les deux. Il sert à être sur les symboles non définis
- Veuillez s'il vous Plaît Oh, jamais changer complètement votre question dans entre. Vous n'êtes pas limité dans le nombre de questions que vous pouvez ouvrir, donc si vous aller plus loin et avoir une autre question, puis de laisser l'ancien one drop et demander un nouveau. Ce n'est pas un forum, c'est une QA site web.
- Vous avez bien appris ma leçon 😉
- pas de problème, il arrive assez souvent 🙂 (et que les gens se poser d'autres questions à réponses)
- J'ai dupliqué une question à trois ans avant que la question a été posée O_o. C'est assez cool 🙂
Vous devez vous connecter pour publier un commentaire.
Comme décrit dans cette étroitement liés à la question qui a été mentionné dans les commentaires,
std::atomic<T>
n'est pas de copier-constructible, ni copie cessible.Les types d'objets qui n'ont pas ces propriétés ne peuvent pas être utilisés comme éléments de
std::vector
.Cependant, il devrait être possible de créer un wrapper autour de la
std::atomic<T>
élément dont la copie est constructible et copiez-cessible. Il faudra utiliser l'load()
etstore()
fonctions de membre destd::atomic<T>
de fournir de construction et d'affectation (c'est l'idée décrite par le a accepté de répondre à la question mentionnée ci-dessus):EDIT: Comme l'a souligné correctement par Bo Persson, l'opération de copie effectuée par le wrapper n'est pas atomique. Il vous permet de copier atomique objets, mais la copie elle-même n'est pas atomique. Ceci signifie que tous les accès simultanés à la atomics ne doit pas faire usage de l'opération de copie. Cela implique que les opérations sur le vecteur lui-même (par exemple l'ajout ou la suppression d'éléments) ne doivent pas être effectuées simultanément.
Exemple: Si, par exemple, un thread modifie la valeur stockée dans l'un des atomics, tandis qu'un autre thread ajoute de nouveaux éléments pour le vecteur, un vecteur de réaffectation peut se produire et l'objet de la première thread modifie peuvent être copiés à partir d'un seul endroit dans le vecteur à l'autre. Dans ce cas, il y aurait de données de la course entre l'accès à l'élément réalisée par le premier fil et l'opération de copie est déclenchée par le second.
_a.store(other._a.load());
n'a pas l'air très atomique pour moi. Est-ce utile?load
etstore
appels pour améliorer les choses, mais comment choisir le bon modèle de mémoire (autres que la sécurité par défaut) est un art.store
, correct? Oui, je pense que c'est vrai. À l'aide destore()
explicitement permet de souligner qu'un atomique de l'exploitation du magasin qui s'y passe.order = seq_cst
par défaut arg, pour une utilisation commeorder
pour les charges. Je suppose qu'il n'y a aucun moyen d'obtenirvector
l'utiliser. Ou peut-être vous devriez défaut derelaxed
depuis ce constructeur est uniquement destiné à être utilisé lors de la copie temporaires pendant la construction ou de la croissance, et vous ne pouvez pas redimensionner / réserve / emplace_back sur un vecteur que d'autres threads sont des pointeurs vers. Surtout pour les.store()
dans le copier-opérateur d'affectation. seq-cst magasins sont chers. (Et vous ne pouvez le faire pendant que vous maintenez un verrou).a[i] = b;
et obtenir un de ces surcharges. L'OP de l'exemple est stupide; la copie à partir d'un autrestd::atomic<int> a_i
est vraiment bizarre.push_back
/emplace_back(int)
(ouconst T&
) pasatomic_int
/const atomic<T> &
. Vous devriez toujours forcer l'utilisateur à charger explicitement la valeur à partir d'un autre objet atomique. (Et bien sûr, un seul thread peut pousser le vecteur parce que le bloc de contrôle n'est pas atomique. Mais si vous avez réservé une capacité suffisante pour garantir l'absence de réaffectation, un thread peut ajouter alors que les autres threads sont de chargement et de stockage des premiers éléments du vecteur.)std::vector
ne semble pas être vrai, au moins en C++11 et au-delà. Des objets sans les copier ou de déplacer des constructeurs peut être utilisé aussi longtemps que vous n'avez pas utiliser les opérations sur les vecteurs qui les obligerait, comme décrit ici. Cela ne signifie pas que vous ne pouvez pas utiliserpush_back
ouemplace_back
, mais ce n'est pas aussi restrictive qu'il y paraît: le déplacement d'unstd::atomic<>
objet en mémoire assez bien les pauses, toutes les accesseurs, il n'est donc pas réaliste de l'opération.D'abord répondre le titre de votre question: vous pouvez déclarer un
std::vector<std::atomic<...>>
facilement, comme vous l'avez fait dans votre exemple.En raison de l'absence de copier ou de déplacer des constructeurs pour
std::atomic<>
objets, cependant, votre utilisation de lavector
sera limité comme vous l'avez découvert avec l'erreur de compilation surpush_back()
. Fondamentalement, vous ne pouvez pas faire quelque chose qui allait invoquer soit le constructeur.Cela signifie que la taille du vecteur doit être fixé à la construction, et vous devez manipuler des éléments à l'aide de
operator[]
ou.at()
. Pour ton exemple de code, les ouvrages suivants1:Si la "taille fixe à la construction de la" limitation est trop onéreux, vous pouvez utiliser
std::deque
à la place. Cela vous permet de emplace objets, la croissance de la structure de façon dynamique, sans exiger de copier ou de déplacer des constructeurs, par exemple:Il y a encore quelques limitations, cependant. Par exemple, vous pouvez
pop_back()
, mais vous ne pouvez pas utiliser le plus généralerase()
. Les limites du sens: uneerase()
au milieu des blocs de stockage contigu utilisé parstd::deque
en général nécessite le déplacement d'éléments, donc nécessite de copier/déplacer constructeur ou de l'affectation des opérateurs à être présent.Si vous ne pouvez pas vivre avec ces limitations, vous pouvez créer une classe wrapper comme suggéré dans d'autres réponses, mais être conscient de l'implémentation sous-jacente: il fait peu de sens pour déplacer un
std::atomic<>
objet une fois qu'il est utilisé: il serait briser tous les threads accèdent simultanément aux objets. La seule saine d'esprit utilisation de copier/déplacer des constructeurs est en général dans la configuration initiale des collections de ces objets avant qu'ils soient publiés sur d'autres threads.1 Sauf, peut-être, vous utilisez Intel
icc
compilateur, qui échoue avec une erreur interne lors de la compilation de ce code.Me semble
atomic<T>
n'a pas de constructeur de copie. Ni un constructeur de déplacement, pour autant que je peux dire.Un travail autour de peut-être utiliser
vector<T>::emplace_back()
pour construire le atomique en place dans le vecteur. Hélas, je n'ai pas de compilateur C++11 sur moi, ou j'irais tester.v_a.push_back(std::move(a_i));
ne résout pas cependant.emplace_back(1)
au lieu depush_back(a_i)
, mais GCC 7.2 rejette que, en disant que les non-opération de copie nécessite le constructeur de copie. Je suppose que c'est dû à d'éventuelles réaffectations lorsqu'un nouvel élément est inséré. De toute façon, même si certains compilateurs accepté, il serait encore à une mauvaise utilisation destd::vector
, au moins formellement, parce que vous n'êtes pas censé utiliser un non-copie-cessible type d'élément.std::vector
basé sur le type de l'élément. Bien sûr, selon le type d'élément que vous utilisez, vous ne serez pas en mesure d'effectuer certaines opérations. Mais si vous n'avez pas besoin de ces opérations, puis en utilisant le type de l'élément doit être tout à fait bien.-D_GLIBCXX_CONCEPT_CHECKS
option, le compilateur va effectuer une série limitée de par la classe des contrôles, et qui n'inclut pas le copier-attribution de vérifier pour vecteur la dernière fois que j'ai vérifié.emplace_back
ne fonctionne pas parce qu'elle peut avoir à pousser le vecteur, et qui nécessite de copier les éléments existants, exigeant de l'inexistant constructeur par copie pour leatomic<T>
éléments. Il n'y a pas de "unsafe_emplace_back" qui ne vérifie pas la capacité. Vous pouvez définir un wrapper pouratomic
qui avait un constructeur de copie, mais probablement juste de ne pas.Comme d'autres l'ont correctement indiqué, la cause de l'compilateur de l'erreur, c'est que std::atomic interdit explicitement le constructeur de copie.
J'ai eu un cas d'utilisation où je voulais la commodité de la STL de la carte (plus précisément, j'ai été en utilisant une carte de cartes afin d'atteindre un clairsemée 2-dimensions de la matrice de atomics si je peux faire quelque chose comme
int val = my_map[10][5]
). Dans mon cas il n'y aurait qu'une seule instance de cette carte dans le programme, donc il n'est pas copié, et même mieux que je peux initialiser l'ensemble de la chose à l'aide d'entretoises d'initialisation. Donc, il est très regrettable que, bien que mon code n'auraient jamais tenter de copier certains éléments ou la carte elle-même, j'ai été empêché d'utiliser un conteneur STL.La solution de contournement que j'ai finalement allé avec est de stocker les std::atomique à l'intérieur d'un std::shared_ptr. Cela a des avantages, mais peut-être un con:
Pour:
Pro ou Con (cet aspect de l'opportunité dépend des programmes de cas d'utilisation"):
- Il n'y a qu'une seule partagé atomique, pour un élément donné. Donc, de copier le shared_ptr ou le conteneur STL sera toujours produire un unique partagé atomique de l'élément. En d'autres termes, si vous copiez le conteneur STL et de modifier l'un des éléments atomiques, l'autre récipient correspondant atomique de l'élément sera également tenir compte de la nouvelle valeur.
Dans mon cas, le Pro/Con caractéristique était discutable en raison de mon cas d'utilisation.
Voici un bref syntaxe pour initialiser un std::vector avec cette méthode: