C++多线程

2.2 向线程函数传递参数 ~ 2.3转移线程所有权

2018-11-25  本文已影响111人  庄阿莫

一、依赖隐式转换的危险

void f(int i,  std::string const& s);
void oops( int some_param )
{
  char buffer[1024];
  sprintf(buffer, "%i", some_param);
  std::thread t(f, 3, buffer);
  t.detach();
}

std::thread t 的构造函数中,想要依赖隐式转换,将字面值buffer转换为期待的 string 对象。但是 std::thread t 的构造函数只会复制buffer

怎么解决?
buffer传递到 std::thread t的构造函数之前,就将它显示转换为 std::string 对象

void f(int i,  std::string const& s);
void not_oops(int some_param)
{
  char buffer[1024];
  sprintf(buffer,  "%i",  some_param);
  std::thread t(f,  3,  std::string(buffer));//显示转换
  t.detach();
}

注意:

class func{
public:
    string &a;
    explicit func(string &a_) : a(a_) {}
};

void fu(string str, func &b)
{
    b.a = str;
}

int main()
{
    string st("hhh");
    string str("嘿嘿");

    func my_func(st);
    std::thread my_thread(fu, str, my_func);//报错
    my_thread.join();

    cout << st << endl;


    return 0;
}

my_thread构造函数无视函数期待的参数类型,并盲目的拷贝my_func。当线程调用fu函数时,传递给函数的参数是my_func内部拷贝的引用,而非它本身的引用。

怎么解决?
使用 std::ref 将参数my_func转换成引用的形式:

std::thread my_thread(fu, str, std::ref(my_func));

当然,也可以使用bind的方式来解决:

class func1{
public:
    int &a;
    explicit func1(int &a_) : a(a_) {}


    int operator()(int c)
    {
        for(;a < c; ++a)
        {
            cout << a << endl;
        }
        cout << a << endl;
        return a;
    }
};

int main()
{
    int st = 6;
    int c = 10;

    func1 f(st);
    /*
     *  f::operator ()(int)实际上是一个operator()(func1 * this, int)
     *  因为类的成员函数,并没有在类内占空间,而是利用签名在类外进行查找
     *  传一个对象引用进去,让函数是针对的是哪个实例
     * */
    std::thread my_thread (&func1::operator(), &f, c);
    my_thread.join();

    return 0;
}

二、一个有趣的情况:提供的参数可以移动,但不能拷贝

"移动"是指:原始对象中的数据转移给另一对象,而转移的这些数据就不再在原始对象中保存了。
比如:std::unique_ptr
移动构造函数(move constructor)和移动赋值操作符(move assignment operator)允许一个对象在多个 std::unique_ptr 实现中传递。在使用"移动"转移原对象后,就会留下一个空指针(NULL)。

当对象是一个临时变量时,自动进行移动操作,例如:string str = string("hhh");。但当对象是一个命名变量,那么转移的时候就需要使用 std::move() 进行显示移动:

void process_big_object( std::unique_ptr<big_object> );

void main()
{
  std::unique_ptr<big_object> p_ob(new big_object);
  p_ob->prepare_data(42);
  std::thread t( process_big_object, std::move(p) ); //①
  
  t.join();
}

①运行过程:
对象p_ob 的所有权就被首先转移到新创建线程的的内部存储中,之后传递给process_big_object函数。

thread 的特性:
std::thread 实例不像 std::unique_ptr 那样能占有一个动态对象的所有权,但是它能占有其他资源:每个实例都负责管理一个执行线程。执行线程的所有权可以在多个 std::thread 实例中互相转移,这是依赖于 std::thread 实例的可移动且不可复制性
不可复制性保证了在同一时间点,一个 std::thread 实例只能关联一个执行线程。
可移动性使得程序员可以自己决定,哪个实例拥有实际执行线程的所有权。

三、转移线程所有权的基础用法

void some_function();
void some_other_function();

std::thread t01(some_function);
std::thread t02 = std::move(t01);//t01的所有权就转移给t02

/*
*  与一个临时 std::thread 对象启动了相关联的线程
*  由于std::thread(some_other_function)是一个临时对象,所以会隐式的调用移动操作
* */
t01 = std::thread(some_other_function);

std::thread t03 = std::move(t02);
t01 = std::move(t03); // ①赋值操作将使程序崩溃

最后一个移动操作使程序崩溃的原因是:
在 t01 将some_function线程的所有权就转移给t02后,t01 又启动了some_other_function线程。当 t03( some_other_function线程 ) 赋给了t01时,由于t01已经有了一个关联的线程,因此系统直接调std::terminate() 终止程序继续运行。这样做是为了保证与 std::thread 的析构函数的行为一致。

四、转移线程所有权的两种想法

1,使用函数来返回新线程的所有权

std::thread f()
{
  void some_function();
  return std::thread(some_function);
} 

std::thread g(int a)
{
  void some_other_function(int);
  std::thread t(some_other_function,a);
  return t;
}

2,在函数中转移线程所有权

void func(std::thread t);
void gunc()
{
  void some_function();
  func( std::thread(some_function) );

  std::thread t(some_function);
  func( std::move(t) );
}

五、迈出线程自动化管理的第一步

为了确保线程程序在主线程退出前完成:

class Threa
{
  std::thread t;

public:

  explicit Threa(std::thread t_): 
      t( std::move(t_) )
  {
    if(!t.joinable())//检测是否为可执行线程,当线程不可加入时,抛出异常
      throw std::logic_error(“No thread”);
  } 

  ~Threa()
  {
    t.join();
  } 

  Threa( Threa const& )=delete;
  Threa& operator= (Threa const&)=delete;
};

struct func
{
  int& i;
  func(int& i_) : i(i_) {}
  void operator() ()
  {
    std::cout << i << std::endl;
  }
};

void f()
{
  int some_local_state;
  th t(std::thread(func(some_local_state)));
  do_something_in_current_thread();
}

当主线程到达f()函数的末尾时,scoped_thread对象开始析构,并阻塞至线程结束。

使用vector实现初步的线程自动化管理

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)); 
//mem_fn把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
}
上一篇下一篇

猜你喜欢

热点阅读