Después de C++11, existe una biblioteca de hilos estándar: std::thread.
Anteriormente, el parámetro de compilación para usar C++11 en algunos compiladores era -std=c++11
g++ -std=c++11 test.cpp
Constructores de std::thread
| Constructor por defecto | thread() noexcept; |
|---|---|
| Constructor de inicialización |
template <class Fn, class... Args> explicit thread(Fn&& fn, Args&&... args); |
| Constructor de copia [eliminado] | thread(const thread&) = delete; |
| Constructor de movimiento (Move) | thread(thread&& x) noexcept; |
- Constructor por defecto, crea uno vacío
std::threadobjeto de ejecución. - Constructor de inicialización, crea un
std::threadobjeto, estestd::threadobjeto puede serjoinable, el hilo recién creado llamará afnla función, los parámetros de dicha función sonargsproporcionados. - Constructor de copia (eliminado), lo que significa que
std::threadel objeto no se puede construir por copia. - Constructor de movimiento: el constructor de movimiento (la semántica de movimiento es un concepto nuevo en C++11, consulte el apéndice). Después de una llamada exitosa,
xno representa ningúnstd::threadobjeto de ejecución.
Nota: los objetos que pueden ser
joinabledestd::threaddeben ser unidos (join) por el hilo principal antes de su destrucciónjoino establecerse como [detached].detached.
std::threadVarios ejemplos de constructores son los siguientes:
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
void f1(int n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << n << " executing\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void f2(int& n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 2 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main()
{
int n = 0;
std::thread t1; // t1 is not a thread
std::thread t2(f1, n + 1); // pass by value
std::thread t3(f2, std::ref(n)); // pass by reference
std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
t2.join();
t4.join();
std::cout << "Final value of n is " << n << '\n';
}
Operaciones de asignación de std::thread
| Operación de asignación por movimiento | thread& operator=(thread&& rhs) noexcept; |
|---|---|
| Operación de asignación por copia [eliminada] | thread& operator=(const thread&) = delete; |
- Operación de asignación por movimiento (1): si el objeto actual no puede ser
joinable, es necesario pasar una referencia rvalue (rhs) a lamoveoperación de asignación; si el objeto actual puede serjoinable, entonces se llamará aterminate() y se generará un error. - La operación de asignación por copia (2) está eliminada, por lo tanto
std::threadel objeto no se puede asignar por copia.
Vea el siguiente ejemplo:
#include <stdio.h>
#include <stdlib.h>
#include <chrono> // std::chrono::seconds
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::sleep_for
void thread_task(int n) {
std::this_thread::sleep_for(std::chrono::seconds(n));
std::cout << "hello thread "
<< std::this_thread::get_id()
<< " paused " << n << " seconds" << std::endl;
}
int main(int argc, const char *argv[])
{
std::thread threads[5];
std::cout << "Spawning 5 threads...\n";
for (int i = 0; i < 5; i++) {
threads[i] = std::thread(thread_task, i + 1);
}
std::cout << "Done spawning threads! Now wait for them to join\n";
for (auto& t: threads) {
t.join();
}
std::cout << "All threads joined.\n";
return EXIT_SUCCESS;
}
Otras funciones miembro
get_id: Obtiene el ID del hilo, devuelve un objeto de tipo std::thread::id. Vea el siguiente ejemplo:
#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t1(foo);
std::thread::id t1_id = t1.get_id();
std::thread t2(foo);
std::thread::id t2_id = t2.get_id();
std::cout << "t1's id: " << t1_id << '\n';
std::cout << "t2's id: " << t2_id << '\n';
t1.join();
t2.join();
}
joinable: Verifica si el hilo puede ser unido (join). Verifica si el objeto de hilo actual representa un hilo de ejecución activo; un hilo creado por el constructor por defecto no puede ser unido. Además, si un hilo ya ha terminado su tarea pero no ha sido unido, el hilo aún se considera un hilo de ejecución activo, por lo que también puede ser unido.
#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t;
std::cout << "before starting, joinable: " << t.joinable() << '\n';
t = std::thread(foo);
std::cout << "after starting, joinable: " << t.joinable() << '\n';
t.join();
}
join: Join 线程,调用该函数会阻塞当前线程,直到由 *this 所标示的线程执行完毕 join 才返回。
#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
// simulate expensive operation
std::this_thread::sleep_for(std::chrono::seconds(1));
}
void bar()
{
// simulate expensive operation
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::cout << "starting first helper...\n";
std::thread helper1(foo);
std::cout << "starting second helper...\n";
std::thread helper2(bar);
std::cout << "waiting for helpers to finish..." << std::endl;
helper1.join();
helper2.join();
std::cout << "done!\n";
}
detach: Detach (separar) el hilo. Separa la instancia de ejecución representada por el objeto de hilo actual de dicho objeto de hilo, de modo que la ejecución del hilo pueda continuar de forma independiente. Una vez que el hilo termina su ejecución, los recursos que se le asignaron serán liberados.
Después de llamar a la función detach:
*thisya no representa ninguna instancia de ejecución de hilo.- joinable() == false
- get_id() == std::thread::id()
Además, si se produce un error o joinable() == false, se lanzará una excepción std::system_error.
#include <iostream>
#include <chrono>
#include <thread>
void independentThread()
{
std::cout << "Starting concurrent thread.\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Exiting concurrent thread.\n";
}
void threadCaller()
{
std::cout << "Starting thread caller.\n";
std::thread t(independentThread);
t.detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Exiting thread caller.\n";
}
int main()
{
threadCaller();
std::this_thread::sleep_for(std::chrono::seconds(5));
}
swap: Intercambia hilos, intercambia los manejadores subyacentes (underlying handles) representados por dos objetos hilo.
#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
void bar()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t1(foo);
std::thread t2(bar);
std::cout << "thread 1 id: " << t1.get_id() << std::endl;
std::cout << "thread 2 id: " << t2.get_id() << std::endl;
std::swap(t1, t2);
std::cout << "after std::swap(t1, t2):" << std::endl;
std::cout << "thread 1 id: " << t1.get_id() << std::endl;
std::cout << "thread 2 id: " << t2.get_id() << std::endl;
t1.swap(t2);
std::cout << "after t1.swap(t2):" << std::endl;
std::cout << "thread 1 id: " << t1.get_id() << std::endl;
std::cout << "thread 2 id: " << t2.get_id() << std::endl;
t1.join();
t2.join();
}
El resultado de la ejecución es el siguiente:
thread 1 id: 1892 thread 2 id: 2584 after std::swap(t1, t2): thread 1 id: 2584 thread 2 id: 1892 after t1.swap(t2): thread 1 id: 1892 thread 2 id: 2584
native_handle: Devuelve el manejador nativo (native handle) (ya que la implementación de std::thread está relacionada con el sistema operativo, esta función devuelve el manejador de hilo correspondiente a la implementación específica de std::thread; por ejemplo, en plataformas que cumplen el estándar Posix (como Unix/Linux) es la biblioteca Pthread).
#include <thread>
#include <iostream>
#include <chrono>
#include <cstring>
#include <pthread.h>
std::mutex iomutex;
void f(int num)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
sched_param sch;
int policy;
pthread_getschedparam(pthread_self(), &policy, &sch);
std::lock_guard<std::mutex> lk(iomutex);
std::cout << "Thread " << num << " is executing at priority "
<< sch.sched_priority << '\n';
}
int main()
{
std::thread t1(f, 1), t2(f, 2);
sched_param sch;
int policy;
pthread_getschedparam(t1.native_handle(), &policy, &sch);
sch.sched_priority = 20;
if(pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch)) {
std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';
}
t1.join();
t2.join();
}
El resultado de la ejecución es el siguiente:
Thread 2 is executing at priority 0 Thread 1 is executing at priority 20
hardware_concurrency [static]: Detecta las características de concurrencia del hardware, devuelve el número de concurrencia de hilos soportado por la implementación de hilos de la plataforma actual, pero el valor de retorno solo sirve como sugerencia (hint) del sistema.
#include <iostream>
#include <thread>
int main() {
unsigned int n = std::thread::hardware_concurrency();
std::cout << n << " concurrent threads are supported.\n";
}
Introducción a las funciones auxiliares relacionadas en el espacio de nombres std::this_thread
get_id: Obtiene el ID del hilo.
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
std::mutex g_display_mutex;
void foo()
{
std::thread::id this_id = std::this_thread::get_id();
g_display_mutex.lock();
std::cout << "thread " << this_id << " sleeping...\n";
g_display_mutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
}
yield: El hilo actual cede la ejecución, y el sistema operativo programa otro hilo para continuar la ejecución.
#include <iostream>
#include <chrono>
#include <thread>
// "busy sleep" while suggesting that other threads run
// for a small amount of time
void little_sleep(std::chrono::microseconds us)
{
auto start = std::chrono::high_resolution_clock::now();
auto end = start + us;
do {
std::this_thread::yield();
} while (std::chrono::high_resolution_clock::now() < end);
}
int main()
{
auto start = std::chrono::high_resolution_clock::now();
little_sleep(std::chrono::microseconds(100));
auto elapsed = std::chrono::high_resolution_clock::now() - start;
std::cout << "waited for "
<< std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
<< " microseconds\n";
}
sleep_until: El hilo se duerme hasta un momento específico (time point), y solo entonces es despertado nuevamente.
template< class Clock, class Duration > void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );
sleep_for: El hilo se duerme durante un intervalo de tiempo (time span) especificado, y solo entonces es despertado nuevamente. Sin embargo, debido a la programación de hilos y otros factores, el tiempo real de sueño puede ser mayor que el intervalo representado por sleep_duration.
#include <iostream>
#include <chrono>
#include <thread>
int main()
{
std::cout << "Hello waiter" << std::endl;
std::chrono::milliseconds dura( 2000 );
std::this_thread::sleep_for( dura );
std::cout << "Waited 2000 ms\n";
}
El resultado de la ejecución es el siguiente:
Hello waiter Waited 2000 ms
Fuente: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Introduction-to-Thread.md