C++
Strutture di sincronizzazione del filo
Ricerca…
introduzione
Lavorare con i thread potrebbe richiedere alcune tecniche di sincronizzazione se i thread interagiscono. In questo argomento, puoi trovare le diverse strutture fornite dalla libreria standard per risolvere questi problemi.
std :: shared_lock
Un shared_lock può essere utilizzato in combinazione con un lock univoco per consentire a più lettori e scrittori esclusivi.
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <string>
#include <iostream>
class PhoneBook {
public:
string getPhoneNo( const std::string & name )
{
shared_lock<shared_timed_mutex> r(_protect);
auto it = _phonebook.find( name );
if ( it == _phonebook.end() )
return (*it).second;
return "";
}
void addPhoneNo ( const std::string & name, const std::string & phone )
{
unique_lock<shared_timed_mutex> w(_protect);
_phonebook[name] = phone;
}
shared_timed_mutex _protect;
unordered_map<string,string> _phonebook;
};
std :: call_once, std :: once_flag
std::call_once
garantisce l'esecuzione di una funzione esattamente una volta dai thread concorrenti. Getta std::system_error
nel caso in cui non possa completare il suo compito.
Utilizzato in combinazione con s td::once_flag
.
#include <mutex>
#include <iostream>
std::once_flag flag;
void do_something(){
std::call_once(flag, [](){std::cout << "Happens once" << std::endl;});
std::cout << "Happens every time" << std::endl;
}
Blocco degli oggetti per un accesso efficiente.
Spesso si desidera bloccare l'intero oggetto mentre si eseguono più operazioni su di esso. Ad esempio, se è necessario esaminare o modificare l'oggetto utilizzando gli iteratori . Ogni volta che è necessario chiamare più funzioni membro è generalmente più efficiente bloccare l'intero oggetto piuttosto che le singole funzioni membro.
Per esempio:
class text_buffer
{
// for readability/maintainability
using mutex_type = std::shared_timed_mutex;
using reading_lock = std::shared_lock<mutex_type>;
using updates_lock = std::unique_lock<mutex_type>;
public:
// This returns a scoped lock that can be shared by multiple
// readers at the same time while excluding any writers
[[nodiscard]]
reading_lock lock_for_reading() const { return reading_lock(mtx); }
// This returns a scoped lock that is exclusing to one
// writer preventing any readers
[[nodiscard]]
updates_lock lock_for_updates() { return updates_lock(mtx); }
char* data() { return buf; }
char const* data() const { return buf; }
char* begin() { return buf; }
char const* begin() const { return buf; }
char* end() { return buf + sizeof(buf); }
char const* end() const { return buf + sizeof(buf); }
std::size_t size() const { return sizeof(buf); }
private:
char buf[1024];
mutable mutex_type mtx; // mutable allows const objects to be locked
};
Quando si calcola un checksum, l'oggetto è bloccato per la lettura, consentendo agli altri thread che desiderano leggere dall'oggetto allo stesso tempo di farlo.
std::size_t checksum(text_buffer const& buf)
{
std::size_t sum = 0xA44944A4;
// lock the object for reading
auto lock = buf.lock_for_reading();
for(auto c: buf)
sum = (sum << 8) | (((unsigned char) ((sum & 0xFF000000) >> 24)) ^ c);
return sum;
}
Cancellare l'oggetto aggiorna i suoi dati interni quindi deve essere fatto utilizzando un blocco di esclusione.
void clear(text_buffer& buf)
{
auto lock = buf.lock_for_updates(); // exclusive lock
std::fill(std::begin(buf), std::end(buf), '\0');
}
Quando si ottiene più di un blocco, è necessario prestare attenzione ad acquisire sempre i blocchi nello stesso ordine per tutti i fili.
void transfer(text_buffer const& input, text_buffer& output)
{
auto lock1 = input.lock_for_reading();
auto lock2 = output.lock_for_updates();
std::copy(std::begin(input), std::end(input), std::begin(output));
}
nota: questo è il modo migliore per usare std :: deferred :: lock e chiamare std :: lock
std :: condition_variable_any, std :: cv_status
Una generalizzazione di std::condition_variable
, std::condition_variable_any
funziona con qualsiasi tipo di struttura BasicLockable.
std::cv_status
come stato di ritorno per una variabile di condizione ha due possibili codici di ritorno:
- std :: cv_status :: no_timeout: non è stato raggiunto il timeout, la variabile di condizione è stata notificata
- std :: cv_status :: no_timeout: la variabile delle condizioni è scaduta