0

我一直在尝试学习如何多线程并得出以下理解。我想知道我是正确的还是遥远的,如果我在任何方面都不正确,是否有人可以给我建议。

要创建线程,首先您需要使用诸如<thread>或任何替代库之类的库(我正在使用 boost 的多线程库来获得跨平台功能)。之后,您可以通过声明它来创建一个线程 (for std::thread)

std::thread thread (foo);

现在,您可以使用thread.join()thread.detach()。前者会等到线程结束,然后再继续;同时,后者将与您计划执行的任何操作一起运行线程。

如果你想保护一些东西,比如 vector std::vector<double> data,不受线程同时访问的影响,你会使用互斥锁。

互斥锁将被声明为全局变量,以便它们可以访问线程函数(或者,如果您正在创建一个将是多线程的类,则可以将互斥锁声明为该类的私有/公共变量)。之后,您可以使用互斥锁锁定和解锁线程。

让我们快速看一下这个示例伪代码:

std::mutex mtx;
std::vector<double> data;
void threadFunction(){
  // Do stuff
  // ...
  // Want to access a global variable
  mtx.lock();
  data.push_back(3.23);
  mtx.unlock();
  // Continue
}

在这段代码中,当互斥锁锁定线程时,它只锁定它和mtx.unlock(). 因此,其他线程仍然会继续他们的快乐方式,直到他们尝试访问数据(注意,我们也可能通过其他线程中的互斥锁)。然后他们会停下来,等待使用数据,锁定它push_back,解锁它并继续。检查here以获得对互斥锁的良好描述。

这就是我对多线程的理解。那么,我是大错特错还是准确无误?

4

1 回答 1

1

您的评论是指“锁定整个线程”。您不能锁定线程的一部分。

当您锁定一个互斥体时,当前线程将获得该互斥体的所有权。从概念上讲,您可以将其视为线程将其标记放在互斥锁上(将其 threadid 存储在互斥锁数据结构中)。如果任何其他线程出现并尝试获取相同的互斥锁实例,它会看到该互斥锁已被其他人“声明”并等待直到第一个线程释放互斥锁。当拥有线程稍后释放互斥锁时,等待互斥锁的线程之一可以唤醒,为自己获取互斥锁,然后继续。

在您的代码示例中,一旦获得互斥锁,就有可能不会释放它。如果对 data.push_back(xxx) 的调用抛出异常(内存不足?),则执行将永远不会到达 mtx.unlock() 并且互斥锁将永远保持锁定状态。尝试获取该互斥锁的所有后续线程都将进入永久等待状态。他们永远不会醒来,因为拥有互斥锁的线程是 toast。

出于这个原因,获取和释放诸如互斥锁之类的关键资源的方式应该保证无论执行如何离开当前范围,它们都会被释放。在其他语言中,这意味着将 mtx.unlock() 放在 try..finally 块的 finally 部分:

mtx.lock();
try
{
    // do stuff
}
finally
{
    mtx.unlock();
}

C++ 没有 try..finally 语句。相反,C++ 利用其语言规则自动处理本地定义的变量。您在局部变量中构造一个对象,该对象在其构造函数中获取一个互斥锁。当执行离开当前函数作用域时,C++会确保对象被释放,对象释放时释放锁。那就是其他人提到的RAII。RAII 只是利用了包含每个 C++ 函数体的现有隐式 try..finally 块。

于 2013-10-16T23:25:09.497 回答