一.线程管控


一.发起线程


类成员函数作为线程函数

#include <iostream>
#include <thread>

class X
{
public:
	void f(){
		std::cout << "hello" << std::endl;
	}
private:

};


void oops()
{
	X my_x;
	std::thread t(&X::f,&my_x);
	t.join();
}

int main()
{
	oops();
	std::cin.get();
}

传入可调用对象作为std::thread构造函数的参数代码

#include <thread>
#include <iostream>

void do_something(int& i)
{
    ++i ;
}

struct func
{
    int& i;

    func(int& i_) :i(i_) {}
    //func(func& j) = delete;
    void operator()()
    {
        std::cout << "3.取新线程内部的可调用对象this指针:\n\t" << this << std::endl;
        std::cout << "4.取新线程内部的可调用对象的成员变量i地址:\n\t" << &i << std::endl;
        for(unsigned j=0;j<10000000;++j)
        {       
            do_something(i);
        }
    }
};


void oops()
{
    int some_local_state=0;
    std::cout << "1.取oops()函数作用域内的局部变量some_local_state地址:\n\t" << &some_local_state << std::endl;
    func my_func(some_local_state);
    std::cout << "2.取oops()函数作用域内的局部变量my_func地址:\n\t" << &my_func << std::endl;
    std::thread my_thread(my_func);
    my_thread.detach();
   // my_thread.join();
}

int main()
{
    oops();
    std::cin.get();
}



编译运行,输出信息如图:
在这里插入图片描述

上图红色序号2和3的地址数值不同(可调用对象的内存地址),说明这两处不是同一个对象。

  解析std::thread my_thread(my_func) 在构造实例时,在新线程内部实例化了一个新的func类型对象,这个新的对象拷贝了my_func对象的内容(调用的拷贝构造函数),而不是引用my_func本体。

上图红色序号1和4的地址数值相同,说明这两处是同一个对象

  解析my_func的内部成员int &i是对some_local_state的引用,新线程内部实例化了一个新的func类型对象,拷贝了my_func的内容,这个新的func类型对象的内部成员int &i也是some_local_state引用。

  注意: my_thread.detach() 明确了不等待新线程运行结束。于是oops()退出后,新线程有可能还在继续运行,do_something(i)的下一步调用,会访问已经销毁的局部变量some_local_state


二.等待线程完成

  在std::thread对象销毁前,我们要确保已经调用了join()或detach()。通常,若要分离线程,在线程启动后调用detach()即可,这不成问题。然后,假使打算等待线程结束,则需要小心地选择执行代码的位置来调用join()。原因是,如果线程启动后有异常抛出,而join()尚未执行,则该join()调用会被略过。


在出现异常时等待,运用RAII机制

  RAII,异常安全(exception-safe)C++ 标准保证了当异常发生时,会调用已创建对象的析构函数。
  运用RAII机制确保新线程在函数f()退出前终结,在 ~thread_guard() 中调用 join() 时,先调用 joinable() 判断新线程能否汇合,因为在任何执行线程上 join() 只能被调用一次,如果线程已经汇合过,再次调用 join() 则是错误行为。

#include <thread>

class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_):
        t(t_)
    {}
    ~thread_guard()
    {
        if(t.joinable())
        {
            t.join();
        }
    }
    thread_guard(thread_guard const&)=delete;
    thread_guard& operator=(thread_guard const&)=delete;
};

void do_something(int& i)
{
    ++i;
}

struct func
{
    int& i;

    func(int& i_):i(i_){}

    void operator()()
    {
        for(unsigned j=0;j<1000000;++j)
        {
            do_something(i);
        }
    }
};

void do_something_in_current_thread()
{
    throw - 1;
}


void f()
{
    int some_local_state;
    func my_func(some_local_state);
    std::thread t(my_func);
    thread_guard g(t);
        
    do_something_in_current_thread();
}

int main()
{
    f();
}


三.在后台运行线程

  调用std::thread对象的成员函数detach(),会令线程在后台运行,遂无法与之直接通信。假若线程被分离,就无法等待它完结,也不可能获得与它关联的std::thread 对象,因而无法汇合该线程。然后分离的线程确实仍在后台运行,其归属权和控制权都转移给了C++运行时库,由此保证,一旦线程退出,与之关联的资源都会被正确回收。
  UNIX操作系统中,有些进程叫做守护进程,它们在后台运行且没有对外的用户界面;沿袭这一概念,分离出去的线程常常被成为守护线程。这种线程往往长时间运行。几乎在应用程序的整个生命周期内,它们一直运行,以执行后台任务,如文件系统监控,从对象缓存中清除无用数据项,优化数据结构等。
  另一种模式,就是由分离线程执行"启动后即可自主完成"的任务;我们还能通过分离线程实现一套机制,用于确认线程完成运行。
注意只有joinable() 返回true时,才能调用detach() 分离线程。


四.向线程函数传递参数


错误的传参方式:

void f(int i, std::string const& s)
{
	std::cout << "代码本意,字符串s的内容应是\t" << "123" << std::endl;
	std::cout << "实际的能容是\t" << s << std::endl;
}

void oops()
{
	char buffer[1024];
	sprintf_s(buffer,1024,"%d", 123);
	std::thread t(f,1,buffer);
	t.detach();
}

int main()
{
	oops();
	std::cin.get();
}

编译运行,结果如图:
在这里插入图片描述
  传入的内容是字符串"123",实际显示的是空白。
  buffer是个局部数组指针,我们本来希望将buffer隐式转换成string对象,再将其作为函数参数,可惜转换未能及时发生,新线程的线程函数被调用时,oops()就已经退出了,buffer已经被销毁,继而引发了未定义行为。这是因为:std::thread的构造函数原样复制所提供的参数,并未令其转换成我们预期的参数类型。


参数传入std::thread的构造函数之前完成转换。

在buffer传入std::thread的构造函数之前,就把它转换成string对象。

#include <iostream>
#include <thread>

void f(int i, std::string const& s)
{
	std::cout << "代码本意,字符串s的内容应是\t" << "123" << std::endl;
	std::cout << "实际的内容是\t" << s << std::endl;
}
void oops()
{
	char buffer[1024];
	sprintf_s(buffer,1024,"%d", 123);
	std::thread t(f,1,std::string(buffer));
	t.detach();
}

int main()
{
	oops();
	std::cin.get();
}

运行结果如图:
在这里插入图片描述


线程函数的参数是非const引用,std::ref函数

需求:在新线程函数f()内部,改写oops()作用域内的对象,我们想要f()的参数是非const引用,将对象通过引用方式传入f(),这时候需要使用std::ref方法才能做到。

#include <iostream>
#include <thread>

void f(int i, std::string & s)
{
	s = "ok";
}

void oops()
{
	std:: string s;
	std::thread t(f,1,std::ref(s));
	t.join();
	std::cout << "现在的string对象s的内容是:\t" << s << std::endl;
}

int main()
{
	oops();
	std::cin.get();
}

运行结果如图:
在这里插入图片描述


线程函数的参数只能移动不能复制,std::move函数

#include <iostream>
#include <thread>
#include <memory>
void f(std::unique_ptr<std::string> ptr)
{
	std::cout << ptr->c_str() << std::endl;
}
void oops()
{
	std::unique_ptr<std::string> ptr(new std::string("hello"));
	std::thread t(f,std::move(ptr));
	t.join();
}
int main()
{
	oops();
	std::cin.get();
}

五. 转移线程归属权

void f1();
void f2();

1.创建线程1,并使之与t1关联,代码如下:

std::thread t1(f1);
  1. t1将线程1的归属权移交给了t2,现在t2接管了线程1,t1内部不再关联任何线程。
    代码如下
std::thread t2 = std::move(t1);
  1. 创建线程2,线程2与临时的std::thread对象关联在一起,临时的std::thread对象移交线程2的归属权给t1,现在t1与线程2关联,代码如下:
t1 = std::thread(f2);
  1. 构造一个std::thread对象,未关联任何执行线程
std::thread t3;

5.将t2相关联的线程1的归属权移交给t3,现在t2不再与任何线程相关联

t3 = std::move(t2);

错误:试图向正管控着一个线程的std::thread对象赋值

6.t1关联着线程2,却试图将t3关联的线程1归属权转移给t1,错误代码如下:

t1 = std::move(t3);

从函数内部返回std::thread对象

std::thread test()
{
  void f();
  return  std::thread(f);
}
std::thread test()
{
  void f();
  std::thread t = std::thread(f);
  return  t;
}

向函数内部传入std::thread对象

void test(std::thread t)void run()
{
  void f();
  test(std::thread t(f));
  std::thread t(f);
  test(std::move(t));
}

std::thread作为类成员

#include <thread>
#include <utility>
#include <iostream>

class scoped_thread
{
    std::thread t;
public:
    explicit scoped_thread(std::thread t_):
        t(std::move(t_))
    {
        if(!t.joinable())
            throw std::logic_error("No thread");
    }
    ~scoped_thread()
    {
        t.join();
    }
    scoped_thread(scoped_thread const&)=delete;
    scoped_thread& operator=(scoped_thread const&)=delete;
};

void do_something(int& i)
{
    ++i;
}

struct func
{
    int& i;

    func(int& i_):i(i_){}

    void operator()()
    {
        for(unsigned j=0;j<1000000;++j)
        {
            do_something(i);
        }
    }
};

void do_something_in_current_thread()
{}

void f()
{
    int some_local_state =0;
    scoped_thread t(std::thread(func(some_local_state)));
        
    do_something_in_current_thread();
}

int main()
{
    f();
}

std::thread存放入容器内

#include <vector>
#include <thread>
#include <algorithm>
#include <functional>

void do_work(unsigned id)
{}

void f()
{
    std::vector<std::thread> threads;
    for(unsigned i=0;i<20;++i)
    {
        threads.push_back(std::thread(do_work,i));
    }
    std::for_each(threads.begin(),threads.end(),
        std::mem_fn(&std::thread::join));
}

int main()
{
    f();
}

六. 识别线程std::thread::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();
}

七. 获取硬件支持的并发线程数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值