C++标准库从C++11开始提供了线程的支持,本文为《C++并发编程实战》第二章的学习笔记。
先从一个简单的例子开始:
#include
#include
void test()
{
std::cout << "hello world!" << std::endl;
}
int main()
{
//hello函数会在新的线程中执行
std::thread t(test);
//join会在调用线程等待std::thread对象相关联的线程结束
t.join();
system("pause"); //msvc
return 0;
}
使用C++线程库来开始一个线程需要构造一个std::thread对象,std::thread可以与任何可调用类型一同工作。一旦开启了线程,需要显式的决定是等待其结束(join)还是让他在后台运行(detach),如果在std::thread对象销毁前还未作决定,那么程序会被终止(std::thread的析构函数调用std::terminate())。调用detach后,没有直接的方法与之通信,也不能再join等待他结束。detach分离的线程所有权和控制权被转交给C++运行时库,以确保与线程相关的资源能被正确回收。
先看看普通函数的调用方式:
#include
#include
#include
void hello()
{
std::cout << "hello world!" << std::endl;
}
void hello(std::string name)
{
std::cout << "hello " << name << std::endl;
}
int main()
{
//因为重载了,所以这里用static_cast转换一下
std::thread t1(static_cast(hello));
std::thread t2(static_cast(hello), "gongjianbo");
t1.join();
t2.join();
system("pause"); //msvc
return 0;
}
对于类成员函数,重载可以借用std::bind,lambda,static_cast等来调用:
#include
#include
#include
#include
class MyClass
{
public:
void test1() {
std::cout << "test1 method" << std::endl;
}
void test2() {
std::cout << "test2" << std::endl;
}
void test2(int i) {
std::cout << "test2 " << i << std::endl;
}
void test2(std::string str) {
std::cout << "test2 " << str << std::endl;
}
};
int main()
{
MyClass obj;
std::thread t(&MyClass::test1, &obj);
t.join();
using Func = void(MyClass::*)();
Func f1 = &MyClass::test2;
std::thread t1(std::bind(f1, obj)); //obj->test2()
void(MyClass::*f2)(int) = &MyClass::test2;
std::thread t2(std::bind(f2, obj, 1992));//obj->test2(int)
std::thread t3([&obj]() { obj.test2(1993); });//obj->test2()
std::thread t4(static_cast(&MyClass::test2),&obj,"gongjianbo");
t1.join();
t2.join();
t3.join();
t4.join();
system("pause"); //msvc
return 0;
}
输出如下(因为并行执行,所以顺序是乱的):
传递参数的引用,可以借助std::ref来实现。默认情况下,std::thread无视函数所期望的类型,并且盲目地复制了所提供的值,对于引用参数,他将传递一个副本的引用而非该对象本身的引用。
void method(int i,type & data);
...
type data;
std::thread t(method,1992,std::ref(data));
std::thread的实例是可移动(movable)但不可复制的(copyable),在任意时刻只有一个对象与某个特定的执行线程相关联,并且不能通过向管理一个线程的std::thread对象赋新值来舍弃之前的关联线程。
std::thread t1(test);
std::thread t2 = std::move(t1);//t1关联线程被转移到t2
t1 = std::thread(test); //t1与一个新的线程相关联
//不能通过向管理一个线程的std::thread对象赋新值来舍弃之前的关联线程
t1 = std::move(t2); //t1已关联一个线程,再赋值会调用std::terminate()终止程序
并发运行的线程数量指示:std::thread::hardware_concurrency(),在多核cpu上它可能是cpu核心的数量,如果信息不可用可能会返回0。
std::cout << "hardware_currency:" << std::thread::hardware_concurrency() << std::endl;
//我的电脑八核的,输出8
线程标识:std::thread::id,通过std::thread::get_id()获取与之关联的线程id,用std::this_thread::get_id()获取当前线程id。如果两个线程id比较结果相等,则是同一个线程,或都没有关联线程。
std::thread::id main_thread_id;//没有关联线程,0
std::thread t(test);
std::cout << "id:" << main_thread_id
<< " " <
不出意外,会打印三个不同的id值。
最后,了解下线程和进程的区别:(复制粘贴的)
1.线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位(线程不拥有系统资源,但可以访问隶属进程的系统资源)。
2.一个进程可以有一个或多个线程
3.进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),各个线程之间有独立的寄存器,栈等。
4.线程上下文切换比进程上下文切换快得多(切换进程涉及CPU环境的设置,切换线程只需要保存和设置一些寄存器内容,并且进程创建和撤销时,在内存空间、IO设备等分配上都会有很大的开销)。