iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++11中线程、锁和条件变量的介绍
  • 781
分享到

C++11中线程、锁和条件变量的介绍

2023-06-17 09:06:18 781人浏览 独家记忆
摘要

这篇文章主要介绍“c++11中线程、锁和条件变量的介绍”,在日常操作中,相信很多人在C++11中线程、锁和条件变量的介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++11中线程、锁和条件变量的介绍”的疑

这篇文章主要介绍“c++11中线程和条件变量的介绍”,在日常操作中,相信很多人在C++11中线程、锁和条件变量的介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++11中线程、锁和条件变量的介绍”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

std::thread类代表了一个可执行的线程,它来自头文件<thread>。与其它创建线程的api(比如 windows API中的CreateThread)不同的是, 它可以使用普通函数、lambda函数以及仿函数(实现了operator()函数的类)。另外,它还允许向线程函数传递任意数量的参数。

#include <thread> void func()  { // do some work } int main()  {     std::thread t(func);     t.join(); return 0;  }

在上面的例子中,t是一个线程对象,函数func()运行于该线程之中。调用join函数后,该调用线程(本例中指的就是主线程)就会在join进来进行执行的线程t结束执行之前,一直处于阻塞状态。如果该线程函数执行结束后返回了一个值,该值也将被忽略。不过,该函数可以接受任意数量的参数。

void func(int i, double d, const std::string& s)  {      std::cout << i << ", " << d << ", " << s << std::endl;  } int main()  {     std::thread t(func, 1, 12.50, "sample");     t.join(); return 0;  }

尽管我们可以向线程函数传递任意数量的参数,但是,所有的参数都是按值传递的。如果需要将参数按引用进行传递,那么就一定要象下例所示一样,把该参数封装到 std::ref或者std::cref之中。

void func(int& a)  {     a++;  } int main()  { int a = 42;     std::thread t(func, std::ref(a));     t.join();        std::cout << a << std::endl; return 0;  }

上面程序打印结果为43,但要不是将a封装到std::ref之中的话,输出的将是42。

除join方法之外,这个线程类还提供了另外几个方法:

  • swap: 将两个线程对象的底层句柄进行交换

  • detatch: 允许执行该方法的线程独立于本线程对象的执行而继续执行。脱离后的线程就再也不能执行join了(你不能等待到它执行结束了)

<span style="font-family:'Courier New', Arial;font-size:9pt;line-height:1.5;">int</span><span style="font-family:'Courier New', Arial;font-size:9pt;line-height:1.5;"> main()</span> {      std::thread t(funct);      t.detach(); return 0;  }

有一点非常重要,值得注意:线程函数中要是抛出了异常的话,使用通常的try-catch方式是捕获不到该异常的。换句话说,下面这种做法行不通:

try {      std::thread t1(func);      std::thread t2(func);         t1.join();      t2.join();  } catch(const std::exception& ex)  {      std::cout << ex.what() << std::endl;  }

要在线程间传递异常,你可以先在线程函数中捕获它们,然后再将它们保存到一个合适的地方,随后再让另外一个线程从这个地方取得这些异常。

std::vector<std::exception_ptr>  g_exceptions; void throw_function()  { throw std::exception("something wrong happened");  } void func()  { try {        throw_function();     } catch(...)     {        std::lock_guard<std::mutex> lock(g_mutex);        g_exceptions.push_back(std::current_exception());     }  } int main()  {     g_exceptions.clear();      std::thread t(func);     t.join(); for(auto& e : g_exceptions)     { try { if(e != nullptr)           {              std::rethrow_exception(e);           }        } catch(const std::exception& e)        {           std::cout << e.what() << std::endl;        }     } return 0;  }

要获得更多关于捕获并传递异常的知识,你可以阅读在主线程中处理工作线程抛出的C++异常以及怎样才能在线程间传递异常?。

在深入讨论之前还有一点值得注意,头文件<thread>里还在命名空间std::this_thread中提供了一些辅助函数:

  • get_id: 返回胆怯线程的id

  • yield: 让调度器先运行其它的线程,这在忙于等待状态时很有用

  • sleep_for: 将当前线程置于阻塞状态,时间不少于参数所指定的时间段

  • sleep_util: 在指定的时刻来临前,一直将当前的线程置于阻塞状态

在上一个例子中,我需要对g_exceptions这个vector进行同步访问,以确保同一个时刻只能有一个线程向其中压入新元素。为了实现同步,我使用了一个互斥量,并在该互斥量上进行了锁定。互斥量是一个核心的同步原语,C++11的<mutex>头文件中包含了四种不同的互斥量。

  • mutex: 提供了核心的lock()函数和unlock()函数,以及非阻塞式的try_lock()方法,该方法在互斥量不可用时会立即返回。

  • recursive_mutex: 运行在同一线程中,多次获得同一个互斥量。

  • timed_mutex: 同第一条中的mutex类似,但它还带来了另外两个方法try_lock_for()和try_lock_until(),分别用于在某个时间段内或在某个时刻到来之前获得该互斥量。

  • recursive_timed_mutex: 结合了第二条的timed_mutex和第三条的recusive_mutex。

以下所列就是一个使用std::mutex(注意其中get_id()和sleep_for()这两个前文所述的辅助函数的用法)的例子。

#include <iOStream>  #include <thread>  #include <mutex>  #include <chrono>     std::mutex g_lock; void func()  {      g_lock.lock();         std::cout << "entered thread " << std::this_thread::get_id() << std::endl;      std::this_thread::sleep_for(std::chrono::seconds(rand() % 10));      std::cout << "leaving thread " << std::this_thread::get_id() << std::endl;         g_lock.unlock();  } int main()  {      srand((unsigned int)time(0));         std::thread t1(func);      std::thread t2(func);      std::thread t3(func);         t1.join();      t2.join();      t3.join(); return 0;  }

其输出将类似如下所示:

entered thread 10144 leaving thread 10144 entered thread 4188 leaving thread 4188 entered thread 3424 leaving thread 3424

lock()和unlock()这两个方法顾名思义,头一个方法用来对互斥量进行加锁,如果互斥量不可得便会处于阻塞状态;第二个方法用来对互斥量进行解锁。

接下来的这个例子演示的是一个简单的线程安全容器(内部使用的是std::vector)。这个容器具有添加单个元素的add()方法以及添加一批元素的addrange()方法,addrange()方法内只是简单的调用了add()方法。

template <typename T> class container   {      std::mutex _lock;      std::vector<T> _elements; public: void add(T element)       {          _lock.lock();          _elements.push_back(element);          _lock.unlock();      } void addrange(int num, ...)      {          va_list arguments;             va_start(arguments, num); for (int i = 0; i < num; i++)          {              _lock.lock();              add(va_arg(arguments, T));              _lock.unlock();          }             va_end(arguments);       } void dump()      {          _lock.lock(); for(auto e : _elements)              std::cout << e << std::endl;          _lock.unlock();      }  }; void func(container<int>& cont)  {      cont.addrange(3, rand(), rand(), rand());  } int main()  {      srand((unsigned int)time(0));         container<int> cont;         std::thread t1(func, std::ref(cont));      std::thread t2(func, std::ref(cont));      std::thread t3(func, std::ref(cont));         t1.join();      t2.join();      t3.join();         cont.dump(); return 0;  }

这个程序执行起来会进入死锁状态。其原因在于,该容器多次尝试获取同一个互斥量而之前却并没有释放该互斥量,这么做是行不通的。这正是std::recursive_mutex的用武之地,它允许同一个线程多次获得同一个互斥量,可重复获得的最大次数并未具体说明,但一旦查过一定次数,再对lock进行调用就会抛出std::system错误。为了修复上面所列代码的死锁问题(不通过修改addrange方法的实现,让它不对lock和unlock方法进行调用),我们可以将互斥量改为std::recursive_mutex。

template <typename T> class container   {      std::recursive_mutex _lock; // ...   };

经过修改之后,该程序的输出会同如下所示类似:

6334 18467 41 6334 18467 41 6334 18467 41

明眼的读者可能已经发现了,每次调用func()所产生的数字序列都完全相同。这是因为对srad的初始化是要分线程进行的,对srand()的调用只是在主线程中进行了初始化。在其它的工作线程中,srand并没有得到初始化,所以每次产生的数字序列就是完全相同的了。

显式的加锁和解锁可能会导致一定的问题,比如忘了解锁或者加锁的顺序不对都有可能导致死锁。本标准提供了几个类和函数用于帮助解决这类问题。使用这些封装类就能够以相互一致的、RAII风格的方式使用互斥量了,它们可以在相应的代码块的范围内进行自动的加锁和解锁动作。这些封装类包括:

  • lock_guard: 该类的对象在构造之时会试图获得互斥量的拥有权(通过调用lock()实现),而在析构之时会自动释放它所获得的互斥量(通过调用unlock()实现)。这是一个不可复制的类。

  • unique_lock: 是一个通用的互斥量封装类。与lock_quard不同,它还支持延迟加锁、时间锁、递归锁、锁所有权的转移并且还支持使用条件变量。这也是一个不可复制的类,但它是可以移动的类。

使用这些封装类,我们可以象这样来改写我们的容器:

template <typename T> class container   {      std::recursive_mutex _lock;      std::vector<T> _elements; public: void add(T element)       {          std::lock_guard<std::recursive_mutex> locker(_lock);          _elements.push_back(element);      } void addrange(int num, ...)      {          va_list arguments;             va_start(arguments, num); for (int i = 0; i < num; i++)          {              std::lock_guard<std::recursive_mutex> locker(_lock);              add(va_arg(arguments, T));          }             va_end(arguments);       } void dump()      {          std::lock_guard<std::recursive_mutex> locker(_lock); for(auto e : _elements)              std::cout << e << std::endl;      }  };

有人会说,既然dump()方法并不会对容器的状态做出任何修改,所以它应该定义为congst的方法。但要是你真的这么改了之后,编译器就会报告出如下的错误:

&lsquo;std::lock_guard<_Mutex>::lock_guard(_Mutex &)' : cannot convert parameter 1 from &lsquo;const std::recursive_mutex' to &lsquo;std::recursive_mutex &'

互斥量(无论使用的是哪一种实现)必须要获得和释放,这就意味着要调用非常量型的lock()和unlock()方法。所以,从逻辑上讲,lock_guard不能在定义中添加const(因为该方法定义为const的话,互斥量也就必需是const的了)这个问题有个解决办法,可以让 mutex变为mutable的。成为 mutable之后就可以在const函数中对状态进行修改了。不过,这种用法应该只用于隐藏的或者“元”状态(比如,对计算结果或者查询到的数据进行缓存,以供下次调用时直接使用而无需再次计算或查询;再比如,对 只是对对象的实际状态起着辅助作用的互斥量中的位进行修改)。

template <typename T> class container   {     mutable std::recursive_mutex _lock;     std::vector<T> _elements; public: void dump() const {        std::lock_guard<std::recursive_mutex> locker(_lock); for(auto e : _elements)           std::cout << e << std::endl;     }  };

这些封装类都具有可以接受一个用来指导加锁策略的参数的构造器,可用的加锁策略有:

  • defer_lockof typedefer_lock_t: 不要取得互斥量的拥有权

  • try_to_lockof typetry_to_lock_t: 在不会被阻塞的情况下尝试获得互斥量的拥有权

  • adopt_lockof typeadopt_lock_t: 假设调用线程已经获得了互斥量的拥有权

这些策略的定义如下所示:

struct defer_lock_t { };   struct try_to_lock_t { };   struct adopt_lock_t { };   constexpr std::defer_lock_t defer_lock = std::defer_lock_t();   constexpr std::try_to_lock_t try_to_lock = std::try_to_lock_t();   constexpr std::adopt_lock_t adopt_lock = std::adopt_lock_t();

除了这些互斥量的封装类,本标准还提供了几个用来对一个或多个互斥量进行加锁的方法。

  • lock: 使用一种可避免死锁的算法对互斥量进行加锁(通过调用tolock()、try_lock()以及unlock())。

  • try_lock: 通过调用try_lock()i按照参数里指定的互斥量的顺序对多个互斥量进行加锁。

这里举一个造成死锁的例子:我们有一个保存元素的容器,还有一个叫做exchange()的方法,用来将一个元素从一个容器中取出来放入另外一个容器。为了成为线程安全的函数,这个函数通过获得每个容器的互斥量,对两个容器的访问进行了同步处理。

template <typename T> class container   { public:      std::mutex _lock;      std::set<T> _elements; void add(T element)       {          _elements.insert(element);      } void remove(T element)       {          _elements.erase(element);      }  }; void exchange(container<int>& cont1, container<int>& cont2, int value)  {      cont1._lock.lock();      std::this_thread::sleep_for(std::chrono::seconds(1)); // <-- forces context switch to simulate the deadlock  cont2._lock.lock();             cont1.remove(value);      cont2.add(value);         cont1._lock.unlock();      cont2._lock.unlock();  }

假设这个函数是从两个不同的线程中进行调用的,在第一个线程中有一个元素从第一个容器中取出来,放到了第二个容器中,在第二个线程中该元素又从第二个容器中取出来放回到了第一个容器中。这样会导致死锁(如果线程上下文正好在获得第一个锁的时候从一个线程切换到了另一个线程的时候就会发生死锁)。

int main()  {      srand((unsigned int)time(NULL));         container<int> cont1;       cont1.add(1);      cont1.add(2);      cont1.add(3);         container<int> cont2;       cont2.add(4);      cont2.add(5);      cont2.add(6);         std::thread t1(exchange, std::ref(cont1), std::ref(cont2), 3);      std::thread t2(exchange, std::ref(cont2), std::ref(cont1), 6);         t1.join();      t2.join(); return 0;  }

要解决该问题,你可以使用以能够避免死锁的方式获得锁的std::lock:

void exchange(container<int>& cont1, container<int>& cont2, int value)  {      std::lock(cont1._lock, cont2._lock);          cont1.remove(value);      cont2.add(value);         cont1._lock.unlock();      cont2._lock.unlock();  }

条件变量

C++11还提供了对另外一个同步原语的支持,这个原语就是条件变量。使用条件变量可以将一个或多个线程进入阻塞状态,直到收到另外一个线程的通知,或者超时或者发生了虚假唤醒,才能退出阻塞状态。头文件<condition_variable>中包含的条件变量有两种实现:

  • condition_variable: 要求任何想等待该条件变量的线程必需先获得std::unique_lock锁。

  • condition_variable_any: 该实现更加通用,它可以用于任何满足基本条件的锁(只要实现了lock()和unlock()方法即可)。因为它使用起来代价要更高一些(从性能和操作系统的字样的角度讲),所以,应该在只有它所提供的额外的灵活性是必不可少的情况下才会选用它。

下面说说条件变量的工作原理:

  • 必须至少要有一个等待条件变为true的线程。等待中的线程必须首先获得一个unique_lock锁。 该锁将会传递给wait()方法,然后wait()方法会释放互斥量并将该线程暂停,直到条件变量得到相应的信号。当接受到信号,线程被唤醒后,该锁就又被重新获得了。

  • 必须至少要有一个线程发送信号使得条件变为true。信号可以通过调用notify_one()来发送,发用这个方法发送后就会将处于阻塞状态的等待该条件获得信号的线程中的某一个线程(任意一个线程)恢复执行;还可以通过调用notify_all()将等待该条件的所以线程唤醒。

  • 因为在多处理器的环境下,要让条件唤醒成为完全可预测会有一些复杂情况难以克服,所以就会出现一些虚假唤醒。也就是说,线程甚至在没有人向条件变量发送信号的情况下就有可能会被唤醒。因此,在线程唤醒后,仍然需要检测条件是不是还为true。而且因为虚假唤醒可能会多次发生,所以该检测必须用一个循环来进行。

以下代码给出了一个利用状态变量来同步线程的例子:几个工作线程可能在他们运行的时候产生错误并且他们把这些错误放到队列里面。一个记录线程会通过从队列得到并输出错误来处理这些错误代码。当有错误发生的时候,工作线程会发信号给记录线程。记录线程一直在等待着状态变量接收信号。为了防止虚假的唤醒,所以记录线程的等待是发生在一个以检测布尔值(boolean)的循环之中的。

#include <thread>  #include <mutex>  #include <condition_variable>  #include <iostream>  #include <queue>  #include <random>   std::mutex              g_lockprint;  std::mutex              g_lockqueue;  std::condition_variable g_queuecheck;  std::queue<int>         g_codes; bool g_done; bool g_notified; void workerfunc(int id, std::mt19937& generator)  { // print a starting message  {          std::unique_lock<std::mutex> locker(g_lockprint);          std::cout << "[worker " << id << "]\trunning..." << std::endl;      } // simulate work  std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); // simulate error  int errorcode = id*100+1;      {          std::unique_lock<std::mutex> locker(g_lockprint);          std::cout  << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;      } // notify error to be logged  {          std::unique_lock<std::mutex> locker(g_lockqueue);          g_codes.push(errorcode);          g_notified = true;          g_queuecheck.notify_one();      }  } void loggerfunc()  { // print a starting message  {          std::unique_lock<std::mutex> locker(g_lockprint);          std::cout << "[logger]\trunning..." << std::endl;      } // loop until end is signaled  while(!g_done)      {          std::unique_lock<std::mutex> locker(g_lockqueue); while(!g_notified) // used to avoid spurious wakeups  {              g_queuecheck.wait(locker);          } // if there are error codes in the queue process them  while(!g_codes.empty())          {              std::unique_lock<std::mutex> locker(g_lockprint);              std::cout << "[logger]\tprocessing error:  " << g_codes.front()  << std::endl;              g_codes.pop();          }           g_notified = false;      }  } int main()  { // initialize a random generator  std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); // start the logger  std::thread loggerthread(loggerfunc); // start the working threads  std::vector<std::thread> threads; for(int i = 0; i < 5; ++i)      {          threads.push_back(std::thread(workerfunc, i+1, std::ref(generator)));      } // work for the workers to finish  for(auto& t : threads)          t.join(); // notify the logger to finish and wait for it  g_done = true;      loggerthread.join(); return 0;  }  Running this code produces an output that looks like this (notice this output is different with each run because each worker thread works, i.e. sleeps, for a random interval):  [logger]        running...  [worker 1]      running...  [worker 2]      running...  [worker 3]      running...  [worker 4]      running...  [worker 5]      running...  [worker 1]      an error occurred: 101 [worker 2]      an error occurred: 201 [logger]        processing error: 101 [logger]        processing error: 201 [worker 5]      an error occurred: 501 [logger]        processing error: 501 [worker 3]      an error occurred: 301 [worker 4]      an error occurred: 401 [logger]        processing error: 301 [logger]        processing error: 401

如上所示的wait()方法有两个重载:

一个是只有一个唯一锁;这个重载释放锁,封锁线程和把线程加入都是等待这一个状态变量的线程队列里面;当状态变量被信号通知后或者是一个假唤醒发生,这些线程就会被唤醒。但他们中任何一个发生时,锁就被重新获得然后函数返回。

另外一个是对于唯一锁的添加,它也是使用一个循环的谓语直到它返回false;这个重载可以用来防止假式唤醒。它基本上是与以下是等价的:

while(!predicate())      wait(lock);

因此在上例中,通过使用重载的wait函数以及一个验证队列状态(空或不空)的断言,就可以避免使用布尔变量g_notified了:

void workerfunc(int id, std::mt19937& generator)  { // print a starting message  {          std::unique_lock<std::mutex> locker(g_lockprint);          std::cout << "[worker " << id << "]\trunning..." << std::endl;      } // simulate work  std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); // simulate error  int errorcode = id*100+1;      {          std::unique_lock<std::mutex> locker(g_lockprint);          std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;      } // notify error to be logged  {          std::unique_lock<std::mutex> locker(g_lockqueue);          g_codes.push(errorcode);          g_queuecheck.notify_one();      }  } void loggerfunc()  { // print a starting message  {          std::unique_lock<std::mutex> locker(g_lockprint);          std::cout << "[logger]\trunning..." << std::endl;      } // loop until end is signaled  while(!g_done)      {          std::unique_lock<std::mutex> locker(g_lockqueue);           g_queuecheck.wait(locker, [&](){return !g_codes.empty();}); // if there are error codes in the queue process them  while(!g_codes.empty())          {              std::unique_lock<std::mutex> locker(g_lockprint);              std::cout << "[logger]\tprocessing error:  " << g_codes.front() << std::endl;              g_codes.pop();          }      }  }

除了这个wait()重载方法,还有另外两个进行类似重载的等待方法,都有用了一个用来避免虚假唤醒的断言:

  • wait_for: 在条件变量收到信号或者指定的超时发生前,一直都将线程置于阻塞状态。

  • wait_until: 在条件变量收到信号或者指定的时刻到来前,一直都将线程处于阻塞状态。

这两个函数不带断言的重载函数会返回一个cv_status状态,该状态用来表明线程被唤醒了到底是因为发生了超时还是因为条件变量收到了信号抑或是发生了虚假唤醒。

本标准还提供了一个叫做notified_all_at_thread_exit的函数,它实现了一种机制,在该机制下,我们可以通知其它线程,某个给定的线程执行结束了,并销毁了所有的thread_local对象。之所以引入该函数,是因为如果使用了thread_local后,采用join()之外的机制等待线程可能会导致不正确甚至是致命的行为,出现这样的问题是因为 thread_local的析构函数甚至可能会在原本处于等待中的线程继续执行后被执行了而且还可能已经执行完成了。(有关这方面更多的情况可参见N3070和N2880)。 一般情况下,notified_all_at_thread_exitTypically必须正好在线程生成前调用。下面给出一个例子,演示一下 notify_all_at_thread_exit是如何同condition_variable一起使用来对两个线程进行同步处理的:

std::mutex              g_lockprint;  std::mutex              g_lock;  std::condition_variable g_signal; bool g_done; void workerfunc(std::mt19937& generator)  {     {        std::unique_lock<std::mutex> locker(g_lockprint);        std::cout << "worker running..." << std::endl;     }      std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));      {        std::unique_lock<std::mutex> locker(g_lockprint);        std::cout << "worker finished..." << std::endl;     }      std::unique_lock<std::mutex> lock(g_lock);     g_done = true;     std::notify_all_at_thread_exit(g_signal, std::move(lock));  } int main()  { // initialize a random generator  std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count());      std::cout << "main running..." << std::endl;      std::thread worker(workerfunc, std::ref(generator));     worker.detach();      std::cout << "main crunching..." << std::endl;      std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));      {        std::unique_lock<std::mutex> locker(g_lockprint);        std::cout << "main waiting for worker..." << std::endl;     }      std::unique_lock<std::mutex> lock(g_lock); while(!g_done) // avoid spurious wake-ups  g_signal.wait(lock);      std::cout << "main finished..." << std::endl; return 0;  }

如果工作线程是在主线程结束之前结束的,输出将会是如下所示:

main running...  worker running...  main crunching...  worker finished...  main waiting for worker...  main finished...

如果是主线程在工作线程结束之前结束的,输出将会是如下所示:

main running...  worker running...  main crunching...  main waiting for worker...  worker finished...  main finished...

结束语

C++11标准使得C++开发人员能够以一种标准的和平台独立的方式来编写多线程代码。本文一一讲述了标准所支持的线程和同步机制。<thread>头文件提供了名为thread的类(另外还包含了一些辅助类或方法),该类代表了一个执行线程。头文件<mutex>提供了几种互斥量的实现,以及对线程进行同步访问的封装类。头文件<condition_variable>为条件变量提供了两种实现,利用这些实现可以让一个或多个线程进入阻塞状态,直到从收到来自另外一个或多个线程的通知、或者发生超时或虚假唤醒为止才会被唤醒。推荐在这方面再阅读一些别的资料来获得更详细的信息。

到此,关于“C++11中线程、锁和条件变量的介绍”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: C++11中线程、锁和条件变量的介绍

本文链接: https://www.lsjlt.com/news/287999.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++11中线程、锁和条件变量的介绍
    这篇文章主要介绍“C++11中线程、锁和条件变量的介绍”,在日常操作中,相信很多人在C++11中线程、锁和条件变量的介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++11中线程、锁和条件变量的介绍”的疑...
    99+
    2023-06-17
  • 详解C++11中的线程锁和条件变量
    目录线程锁条件变量小结线程 std::thread类, 位于<thread>头文件,实现了线程操作。std::thread可以和普通函数和 lambda 表达式搭配使用。...
    99+
    2024-04-02
  • C++11中线程锁和条件变量的示例分析
    这篇文章主要介绍了C++11中线程锁和条件变量的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。线程std::thread类, 位于<thread>头文件,...
    99+
    2023-06-15
  • 怎么理解C++11 中的线程及锁和条件变量
    今天就跟大家聊聊有关怎么理解C++11 中的线程及锁和条件变量,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。线程类std::thread代表一个可执行线程,使用时必须包含头文件<...
    99+
    2023-06-17
  • C++多线程互斥锁和条件变量的详解
    目录互斥锁:std::mutex::try_lock         条件变量:condition_variable总结我们了解互斥...
    99+
    2024-04-02
  • C++11线程、互斥量及条件变量怎么创建
    这篇“C++11线程、互斥量及条件变量怎么创建”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C++11线程、互斥量及条件变量...
    99+
    2023-07-05
  • C++11 condition_variable条件变量的用法说明
    目录1 什么是条件变量2 condition_variable类定义2.1 wait函数3 condition_variable用法3.1 资源修改线程步骤3.2 资源等待线程步骤4...
    99+
    2024-04-02
  • Python中线程锁的使用介绍
    目录前言方式一:使用try/finally,确保锁肯定会被释放。方式二:with语句避免使用try/finally。总结前言 当有多个线程,且它们同时访问同一资源时,需要考虑如何避免...
    99+
    2024-04-02
  • c++多线程要使用条件变量的原因
    小编给大家分享一下c++多线程要使用条件变量的原因,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!先看示例1:#include <iostream&...
    99+
    2023-06-15
  • C#中异步和多线程的区别介绍
    一、区别和联系 异步和多线程有什么区别?其实,异步是目的,而多线程是实现这个目的的方法。异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没有必要异步了),可以继...
    99+
    2024-04-02
  • python3--线程,锁,同步锁,递归锁,信号量,事件,条件和定时器,队列,线程池
    线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位进程和线程是什么关系?  线程是在进程中的 一个执行单位  多进程 本质上开启的这个进程里就有一个线程  多线程 单纯的在当前进程中开启了多个线...
    99+
    2023-01-30
    递归 线程 信号量
  • c++多线程为何要使用条件变量详解
    先看示例1: #include <iostream> #include <windows.h> #include <mutex> #inclu...
    99+
    2024-04-02
  • Python中的变量,参数和模块介绍
    目录前言1 变量2 参数3 模块前言 简单的使用python函数之后,我们在日常开发中还需要经常使用的三个地方,分别是变量、参数和模块。其中,Python的变量类型已经在语法介绍中做...
    99+
    2024-04-02
  • 线程局部变量的实现ThreadLocal使用及场景介绍
    目录前言ThreadLocal 介绍常用 APIThreadLocal 使用场景Spring 事务管理器SpringMVC 存储上下文 Request 数据PageHelper 分页...
    99+
    2023-01-16
    线程局部变量ThreadLocal ThreadLocal 线程
  • C++中标准线程库的基本使用介绍
    目录1.创建线程异步执行2.通过使用互斥锁防止线程冲突3.采用信号量控制线程的运行4.通过promise实现进程间通信总结Qt的封装程度比较高的线程类用多了,发现C++标准库里面的线...
    99+
    2024-04-02
  • ​ 生产者消费者问题(互斥锁和条件变量的结合)
          本篇文章对生产者消费者(模型)问题进行了详解。其中给出了基于阻塞队列的生产者消费者模型demo代码和对涉及到的条件变量与互斥锁的操作也进行了详细解释。解释了条件变量等待时,为什么还需要一把锁的问题。对生产者消费者(模型)问题...
    99+
    2023-10-10
    开发语言 c++ 生产者消费者问题 模型 多线程
  • C++线程安全容器stack和queue的使用详细介绍
    目录线程安全的容器栈threadsafe_stack线程安全的容器队列threadsafe_queue要构建线程安全的数据结构, 关注几点: 若某线程破坏了数据结构的不变量, 保证其...
    99+
    2024-04-02
  • GoLang并发编程中条件变量sync.Cond的使用
    目录一、条件变量与互斥锁二、条件变量与互斥锁的配合使用三、条件变量的使用创建锁和条件使用四、条件变量的Wait方法做了什么一、条件变量与互斥锁 条件变量是基于互斥锁的,它必须基于互斥...
    99+
    2023-01-10
    Go sync.Cond介绍 Go sync.Cond使用
  • Linux C中多线程与volatile变量的示例分析
    这篇文章主要介绍Linux C中多线程与volatile变量的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Linux C中多线程与volatile变量volatile 修饰的变量表示改变量的值是易变的,编译...
    99+
    2023-06-09
  • C#中的out参数、ref参数和params可变参数用法介绍
    out参数: out关键字 通过引用来传递参数,在定义方法和调用方法的时候都必须使用out关键字 简单来讲out可以用来返回多个参数类型。 static void Ma...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作