最简单和最基本的并发,是指两个或者更多独立的活动同时发生。计算机领域的并发指的是在单个系统里同时执行多个独立的任务,而非顺序的进行一些活动。
以前的计算机只有一个处理器,他的并发其实是任务切换,只是因为切换的太快,我们无法感觉到任务何时会被暂时挂起。而在多处理器的计算机或者是多核的处理器,能真正的并发进行多个任务,称为硬件并发。
在收益比不上成本的时候,不推荐使用并发。使用并发需要跟多编写和维护多线程代码。另一方面,太多线程的同时运行,会消耗很多操作系统资源,而从使得操作系统整体上变得更加缓慢。线程的需要独立的堆栈空间,切换也需要进行上下文切换。
这一点很多帖子都有说过。
多进程并发:将应用程序分为多个进程。由于进程之间的资源并不能直接进行访问,进程之间的资源共享需要通过进行通信来完成,但是进程之间通信较为复杂。同时,操作系统为了对每个进程进行维护需要耗费更多资源,整体开销较大。但是,由于进程的资源保护,使用多进程并发更容易写出安全的并发程序。并且多进程并发可是实现远程连接,通过网络在不同的机器上运行进程。
多线程并发:单个进程运行多个线程。线程是轻量级的进程。多个线程之间共享内存地址。使用多线程并发开销更小,且更为灵活。但是为了保证数据一致性,将要做更多的额外工作。
#include
#include
using namespace std;
void hello(){
for(int i = 0; i < 10; i++)
cout << "hello concurrent \n";
}
int main(){
thread t(hello);
for(int i = 0; i < 10; i++)
cout << "main concurrent \n";
t.join();
}
从一个简单的例子开始,和之前一样,使用hello word。与直接写入标准输出不同,上面代码初试线程main,然后创建新的线程hello。
在C++中,C++11提供了标准库,thread来管理线程。thread库用于线程的管理,对于共享数据和通信的操作需要使用其他的库。不过还有一个pthread头文件,这个是简单粗暴地使用线程。
t.join()这一句是,让main线程等待t线程结束。否则会报terminate called without an active exception。
每一个程序都至少有一个线程,那就是main,其余线程有自己的入口函数,各个线程同时运行。当执行完入口函数后,线程也会退出。
class foo{
public:
void operator() (){
for(int i = 0 ; i < 5; i++)
std::cout << "foo concurrent \n";
}
};
void fun(){
for(int i = 0 ; i < 5; i++)
std::cout << "lamda concurrent \n";
}
int main()
{
using std::thread;
foo my_foo;
thread t1(my_foo); //函数对象
thread t2(fun); //函数
thread t3([]{fun();});//lamda表达式
t1.join();
t2.join();
t3.join();
}
1、hello word中是直接启动,通常是无参数,无返回的函数
2、使用可调用类型构造,将带有函数调用符类型的实例传入std::thread类中,替换默认的函数。注意不能使用临时变量。thread t2(foo());
3、可以使用lambda表达式
class foo{
public:
void operator() (){
for(int i = 0 ; i < 100000; i++)
std::cout << "foo concurrent \n";
}
};
int main()
{
第一种:
std::thread t{foo()};//当main结束后,进程t也被关闭
第二种:
std::thread t2{foo()};
t2.join()//main 等待t2结束后再结束
第三种:
std::thread t3{foo()};
my_thread.detach();//main结束,但t3能继续运行
}
1、在main结束后强行关闭,会爆出异常
2、使用join来阻塞当前线程,只有子线程结束后,当前线程继续执行。使用joinable来判断是否可以被等待
3、detach() 可以对线程进行分离,但是如果foo中有局部变量,这时候新建线程将访问不确定值,将导致出错
int main()
{
std::thread t{foo()};
do_something();
t.join();
}
假设do_something 有异常那么t.join()这条语句也不会执行,所以线程直接会被关闭。
1、使用try{}catch{}块
int main()
{
std::thread t{foo()};
try{
do_something();
}catch(...){
t.join();
throw;
}
t.join();
}
2、资源获取即初始化
class thread_guard
{
thread &t;
public :
explicit thread_guard(thread& _t) :
t(_t){}
~thread_guard()
{
if (t.joinable())//1
t.join();//2
}
thread_guard(const thread_guard&) = delete; //3
thread_guard& operator=(const thread_guard&) = delete;
};
int main()
{
std::thread t{foo()};
thread_guard(t);
do_something();
}//4
将线程变量交由一个类来管理,并在类的析构函数中加入join。当发生异常或者线程正常退出时,管理类的对象就会被销毁,调用析构函数来触发join()函数。
后台运行就是使用detach()让线程在后台,这意味着主线程不能与之进行直接交互,也就是线程分离。分离线程的确在后台运行,不能被加入。
简单的传递,就在函数名称后面进行填写。
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
但值得注意的是,默认的传值方式是值拷贝,thread会用新的地址来接收变量的值,即使函数接受的参数是引用也无效。而且,指向动态变量的指针作为参数传递时,指针指向本地变量,可能在转化的时候崩溃。
如果需要传递,需要使用std::ref()函数
class X
{
public:
void do_lengthy_work(int a);
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x,a); // 1
第一个函数参数为成员函数的地址,第二个参数为对象的地址,后面的才是成员函数的参数。值得注意的是,提供的参数可以移动,但不能拷贝。
void some_function();
void some_other_function();
std::thread t1(some_function); // 1
std::thread t2=std::move(t1); // 2
t1=std::thread(some_other_function); // 3
std::thread t3; // 4
t3=t2;//赋值将崩溃
t3=std::move(t2); // 5
t1=std::move(t3);//6 赋值将崩溃
我们可以看出同一个线程在多个对象之间进行了传递。但是不能直接赋值,只能move。并且,在6之前,步骤2已经将t1和线程绑定,就不能在和t3的线程绑定。
使用std::thread::hardware_concurrency()函数可以获取系统能同时并发在一个程序中的线程数量。通过此数值,我们可以设置启动时的最佳线程数。
线程标志类型是 thread::id,可以通过两种方法进行检索
1、用 std::thread 对象的成员函数 get_id() 来直接获取
std::thread t{ foo() };
std::thread::id id;
id=t.get_id();
2、
auto std::this_thread::get_id();