c++ 11 之后有了标准的线程库:std::thread。通过c++11中的线程库创建线程,极为方便,且跨平台,是语言层面的。之前跨平台的多线程开发中,多使用boost 相关第三方库。

在main函数中 启动另外线程调用 C++ 匿名函数、参数

#include <iostream>
#include <thread>

int main() {
    std::thread t([](){
        // 在这里编写你的匿名函数的代码
        std::cout << "Hello from the new thread!" << std::endl;
    });

    // 主线程继续执行其他操作

    t.join(); // 等待新线程执行完毕

    return 0;
}

1.std::thread 构造函数

默认构造函数 		thread() noexcept;
初始化构造函数 	template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args);
拷贝构造函数 [deleted] 	thread(const thread&) = delete;
Move 构造函数 	thread(thread&& x) noexcept;

(1).默认构造函数,创建一个空的 std::thread 执行对象。
(2).初始化构造函数,创建一个 std::thread 对象,该 std::thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
(3).拷贝构造函数(被禁用),意味着 std::thread 对象不可拷贝构造。
(4).Move 构造函数,move 构造函数(move 语义是 C++11 新出现的概念,详见附录),调用成功之后 x 不代表任何 std::thread 执行对象。
注意:可被 joinable 的 std::thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.

std::thread 各种构造函数例子如下:

#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,t1不是一个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,t4执行t3,t3不是thread
    t2.join();
    t4.join();
    std::cout << "Final value of n is " << n << '\n';
}

2.std::thread 赋值操作

Move 赋值操作 	thread& operator=(thread&& rhs) noexcept;
拷贝赋值操作 [deleted] 	thread& operator=(const thread&) = delete; 

(1).Move 赋值操作(1),如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;如果当前对象可被 joinable,则会调用 terminate() 报错。’
(2).拷贝赋值操作(2),被禁用,因此 std::thread 对象不可拷贝赋值。

请看下面的例子:

#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;
}

3.其他成员函数

3.1 get_id()

获取线程 ID,返回一个类型为 std::thread::id 的对象。

请看下面例子:

//test.cpp
#include <iostream>
#include <thread>
#include <chrono> //该头文件详情可参考:https://www.cnblogs.com/jwk000/p/3560086.html

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();
  return 0;
}
//编译g++ -std=c++11 -pthread test.cpp -o test

从std::thread::id取得int值线程id

std::thread::id tid = std::this_thread::get_id();
_Thrd_t t = *(_Thrd_t*)(char*)&tid ;
unsigned int nId = t._Id

参考资料:https://www.cnblogs.com/yc-only-blog/p/9178935.html

3.2 joinable()

检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。另外,如果某个线程 已经执行完任务,但是没有被 join 的话,该线程依然会被认为是一个活动的执行线程,因此也是可以被 join 的。

#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: " << std::boolalpha << t.joinable() << '\n';
 
    t = std::thread(foo);//此时子线程并不执行,在调json()或detach()后才执行
    std::cout << "after starting, joinable: " << t.joinable() << '\n';
 
    t.join();//说明:如果此处不调用join(),也不调用detach(),编译执行程序就会提示 terminate called without an active exception

    std::cout << "after joining, joinable: " << t.joinable() << '\n';
}
//join: Join 线程,调用该函数会阻塞当前线程,直到由 *this 所标示的线程执行完毕 join 才返回。

std::thread “terminate called without an active exception”请参考如下接:
https://www.cnblogs.com/little-ant/p/3312841.html

3.3 detach()

Detach 线程,将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。

调用 detach 函数之后:

*this 不再代表任何的线程执行实例。
joinable() == false
get_id() == std::thread::id()

另外,如果出错或者 joinable() == false,则会抛出 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));
}

3.4 swap()

Swap 线程,交换两个线程对象所代表的底层句柄(underlying handles)。

#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();
}
执行结果如下:

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

3.5 native_handle

返回 native handle(由于 std::thread 的实现和操作系统相关,因此该函数返回与 std::thread 具体实现相关的线程句柄,例如在符合 Posix 标准的平台下(如 Unix/Linux)是 Pthread 库)。

3.6 hardware_concurrency [static]

检测硬件并发特性,返回当前平台的线程实现所支持的线程并发数目,但返回值仅仅只作为系统提示(hint)。

#include <iostream>
#include <thread>
 
int main() {
    unsigned int n = std::thread::hardware_concurrency();
    std::cout << n << " concurrent threads are supported.\n";
}

4.std::this_thread 命名空间中相关辅助函数介绍

4.1 get_id()

获取线程 ID。

#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();
}

4.2 yield()

当前线程放弃执行,操作系统调度另一线程继续执行。

#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();//让cpu执行其他空闲的线程
  } 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";
}

4.3 sleep_until()

线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

template< class Clock, class Duration >
void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );

4.4 sleep_for()

线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 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";
}

5.std::thread堆栈使用

#include <iostream>
#include <thread>
using namespace std;
void show()
{
	cout << "hello cplusplus!" << endl;
}
int main()
{
	//栈上
	thread t1(show);   //根据函数初始化执行
	thread t2(show);
	thread t3(show);
	t1.join();
	t2.join();
	t3.join();
	//线程数组
	thread threads[3]{thread(show), thread(show), thread(show)}; 
	for (auto& t: threads) 
	{ 
		t.join();
	}
	
	//堆上
	thread *pt1(new thread(show));
	thread *pt2(new thread(show));
	thread *pt3(new thread(show));
	pt1->join();
	pt2->join();
	pt3->join();
	//线程指针数
	thread *pthreads(new thread[3]{thread(show), thread(show), thread(show)});
	

	delete pt1;
	delete pt2;
	delete pt3;
	// delete [] pthreads;//这里直接delete会导致异常退出,因为线程没有join(),也没有detach()

	
	int threadNum = 3;
	thread* t = new thread[threadNum];
	if(t)
	{
		for ( int i = 0; i < threadNum; i++){
        	t[i] = thread(show);    
	    }

	    for ( int i = 0; i < threadNum; i++){
	        //t[i].detach(); //主进程不等子进程运行完
	        t[i].join();        //主进程等
	    }
	}
	delete [] t;
	return 0;
}

注意事项:
1.std::thread在栈内创建后,需要在同一个作用域内调用join() 或者 detach(), 否则退出作用域后,程序会异常退出, 具体原因如下

~thread()
{
	if (joinable())
		std::terminate();
}

其中的std::terminate()就是用来终止进程的调用。由于std::thread创建后默认是joinable的, 所以需要调用join()或者detach() 来使线程变成非joinable的状态, 具体使用join()还是detach() 取决于实际需求, 如果需要等待线程完成才能继续,那就使用join()来等待, 如果需要立刻返回继续其他操作, 那就调用detach()来脱离对线程的管理, 两者必须选一个。

2.调用new 创建的std::thread, 禁止直接使用delete, 需要在调用delete之前,调用 join()或者 detach() (如果创建线程后立刻调用了这两个函数中任意一个, 可以不再调用, 但是为了保险起见, 需要加上if(joinable()) 的判断), 原因见上一条。

6.std::thread 线程执行体

std::thread 执行体类似boost::thread, 并不要求是普通的函数,任何可调用的对象都可,具体接受下面四种:

  1. 普通函数
  2. 函数对象
  3. 类成员函数
  4. lambda 函数(匿名函数)
    分别使用举例如下:

(1) 普通函数

void hello() //不带参数 
{
	//do whatever you want 
} 
void hello(std::string name) //带参数 
{
	//do whatever you want using parameter in 
} 
std::thread thread1(hello); 
std::thread thread2(hello, "test");

(2) 函数对象

class Hello // 不带参数 
{
	public: Hello(){} 

	//over ride operator() 
	void operator()() const 
	{
		//do whatever you want
	} 
};

class Hello {
	public: Hello() {} 

	//over ride operator() 
	void operator()(const std::string name) const 
	{
		// do whatever you want using parameter in
	} 
};

std::thread thread3(Hello()); 
std::thread thread4(Hello(), "test");

(3) 类成员函数

class Hello {
	public: entity() 
	{
		// do whatever you want 
	} 

	//over ride operator() 
	void entity(const std::string name) 
	{
		// do whatever you want using parameter in name
	} 
};

Hello obj;
std::thread thread5(&Hello::entity, &obj);
std::thread thread6(&Hello::entity,  &obj, "test");
// std::thread thread5(&Hello::entity, this);
// std::thread thread6(&Hello::entity,  this, "test");

此种构造也可以借助std::bind使用另外一种方式:

std::thread  thread5(std::bind(&Hello::entity, &obj));
std::thread  thread6(std::bind(&Hello::entity,  &obj, "test"));

(4) lambda 函数

std::thread thread7([](T var1, T var2){ 
	//do whatever you want using parameter var1 var2
}, realValue1, readValue2);

//某匿名函数定义 
//auto fun = [](const char *str) {cout << str << endl; };

7.原子变量与线程安全

示例1:

#include <iostream>
#include <thread>
using namespace std;
const int N = 100000000;
int num = 0;
void run()
{
	for (int i = 0; i < N; i++)
	{
		num++;
	}
}
int main()
{
	clock_t start = clock();
	thread t1(run);
	thread t2(run);
	t1.join();//join之后开始执行子线程
	t2.join();//join之后开始执行子线程
	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " us" << endl;
	return 0;
}
//运行结果:
//num=100445432,用时 1013773 us
//注意linux下clock()单位是微妙,windows下clock()单位是毫秒

从上述代码执行的结果,发现结果并不是预计的200000000,这是由于线程之间发生冲突,从而导致结果不正确。为了解决此问题(多线程同时操作同一份数据),有以下方法:
(1)互斥量。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
const int N = 100000000;
int num = 0;
mutex m;
void run()
{
	for (int i = 0; i < N; i++)
	{
		m.lock();
		num++;
		m.unlock();
	}
}
int main()
{
	clock_t start = clock();
	thread t1(run);
	thread t2(run);
	t1.join();
	t2.join();
	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " us" << endl;
	return 0;
}
//运行结果:
//num=200000000,用时 40520541 us

通过互斥量后运算结果正确,但是计算速度很慢,原因主要是互斥量加解锁需要时间。
互斥量详细内容 请参考C++11 并发之std::mutex
(2)原子变量。
例如:

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
const int N = 100000000;
atomic_int num{ 0 };//不会发生线程冲突,线程安全
void run()
{
	for (int i = 0; i < N; i++)
	{
		num++;
	}
}
int main()
{
	clock_t start = clock();
	thread t1(run);
	thread t2(run);
	t1.join();
	t2.join();
	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " us" << endl;
	return 0;
}
//运行结果:
//num=200000000,用时 7111836 us

通过原子变量后运算结果正确,计算速度一般。
(3)通过控制 join位置 。

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
const int N = 100000000;
int num = 0;
void run()
{
	for (int i = 0; i < N; i++)
	{
		num++;
	}
}
int main()
{
	clock_t start = clock();
	thread t1(run);
	t1.join();
	thread t2(run);
	t2.join();
	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " us" << endl;
	return 0;
}
//运行结果:
//num=200000000,用时 485837 us

运算结果正确,计算速度也很理想。看似没有什么问题,实际上适用场景非常有限,仅适用于一个线程去控制一份数据的情况,大型项目中线程层级关系较多,还是通过数据互斥锁或者设置原子变量的方法来控制

编译 g++ -std=c++11 -pthread test.cpp -o test.exe

参考资料:

示例

// Linux编译:g++ test_thread.cpp -lpthread
#include <iostream>
#include <thread>
#include <atomic>
#include <functional>
#include <mutex>

class CTestThread
{
private:
    /* data */
public:
   
void startThread()
{
    std::lock_guard<std::mutex> g(m_threadMtx);
    if(!m_p_thread){
        m_running = true;
        m_p_thread = new std::thread(std::bind(&CTestThread::runThread,this));
    }
}
void stopThread()
{
    std::lock_guard<std::mutex> g(m_threadMtx);
    if(m_p_thread){
        m_running = false;
        m_p_thread->join();
        delete m_p_thread;
        m_p_thread = nullptr;
    }
}
void runThread()
{
    int num = 0;
    while(m_running)
    {
        std::cout << "thread id:" << std::this_thread::get_id() << " num: " << num++ << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
}


void startThread2()
{
    if(!m_threadrunning){
        m_threadrunning = true;
        m_thread = std::move(std::thread(std::bind(&CTestThread::runThread,this)));
    }
}
void stopThread2()
{
    if(m_threadrunning){
        m_threadrunning = false;
        if (m_thread.joinable())
            m_thread.join();
    }
}

void startThread3()
{
    if(!m_threadrunning){
        m_threadrunning = true;
        m_thread = std::move(std::thread([this](){runThread();}));
    }
}
void stopThread3()
{
    if(m_threadrunning){
        m_threadrunning = false;
        if (m_thread.joinable())
            m_thread.join();
    }
}
 
private:
    std::mutex m_threadMtx;
    std::thread *m_p_thread = nullptr;
    bool m_running = false;


    std::thread m_thread;
    bool m_threadrunning = false;
};

void hello(std::string msg)\
{
    std::cout << "hello  " << msg << std::endl;
}

int main(int argc, char* argv[])
{
    CTestThread testThread;
    testThread.startThread();
    testThread.startThread2();

    CTestThread testThread3;
    testThread3.startThread3();


    std::thread th([]()->void{
        int count = 0;
        while(1){
            count++;
            std::cout << "count: " << count << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    });
    th.detach();

    std::thread th2([=](){
        int age = 0;
        while(1){
            age++;
            std::cout << "age: " << age << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    });
    th2.join(); // 线程不退出会阻塞在这里

    std::thread th4(hello, "LiMing");


// std::thread
// std::bind
// std::move
// 匿名函数
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐