日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++同步線程實現示例詳解_C 語言

作者:無水先生 ? 更新時間: 2022-12-15 編程語言

一、同步線程

雖然使用多線程可以提高應用程序的性能,但通常也會增加復雜性。如果同時執行多個函數,則必須同步對共享資源的訪問。一旦應用程序達到一定大小,這將涉及大量的編程工作。本節介紹Boost.Thread提供的用于同步線程的類。

二、獨占訪問示例

示例 44.7。使用 boost::mutex 的獨占訪問

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
boost::mutex mutex;
void thread()
{
  using boost::this_thread::get_id;
  for (int i = 0; i < 5; ++i)
  {
    wait(1);
    mutex.lock();
    std::cout << "Thread " << get_id() << ": " << i << std::endl;
    mutex.unlock();
  }
}
int main()
{
  boost::thread t1{thread};
  boost::thread t2{thread};
  t1.join();
  t2.join();
}

多線程程序使用互斥體進行同步。 Boost.Thread 提供了不同的互斥類,其中 boost::mutex 是最簡單的。互斥量的基本原理是防止其他線程在特定線程擁有互斥量時取得所有權。一旦釋放,不同的線程就可以取得所有權。這會導致線程等待,直到擁有互斥鎖的線程完成處理并釋放其對互斥鎖的所有權。

示例 44.7 使用了一個名為 mutex 的 boost::mutex 類型的全局互斥體。 thread() 函數通過調用 lock() 獲得此對象的所有權。這是在函數寫入標準輸出流之前完成的。寫入消息后,通過調用 unlock() 釋放所有權。

main() 創建兩個線程,這兩個線程都在執行 thread() 函數。每個線程計數為 5,并在 for 循環的每次迭代中將消息寫入標準輸出流。因為 std::cout 是線程共享的全局對象,所以訪問必須同步。否則,消息可能會混淆。同步保證在任何給定時間,只有一個線程可以訪問 std::cout。兩個線程都嘗試在寫入標準輸出流之前獲取互斥鎖,但一次只有一個線程實際訪問 std::cout。無論哪個線程成功調用 lock(),所有其他線程都需要等到 unlock() 被調用。

獲取和釋放互斥鎖是一個典型的方案,Boost.Thread通過不同的類型來支持。例如,您可以使用 boost::lock_guard 而不是使用 lock() 和 unlock()。

示例 44.8。 boost::lock_guard 保證互斥釋放

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
boost::mutex mutex;
void thread()
{
  using boost::this_thread::get_id;
  for (int i = 0; i < 5; ++i)
  {
    wait(1);
    boost::lock_guard<boost::mutex> lock{mutex};
    std::cout << "Thread " << get_id() << ": " << i << std::endl;
  }
}
int main()
{
  boost::thread t1{thread};
  boost::thread t2{thread};
  t1.join();
  t2.join();
}

boost::lock_guard 分別在其構造函數和析構函數中自動調用 lock() 和 unlock()。對共享資源的訪問在示例 44.8 中是同步的,就像顯式調用兩個成員函數時一樣。類 boost::lock_guard 是 RAII 習慣用法的一個示例,用于確保資源在不再需要時被釋放。

除了 boost::mutex 和 boost::lock_guard,Boost.Thread 還提供了額外的類來支持同步的變體。其中一個重要的是 boost::unique_lock ,它提供了幾個有用的成員函數。

示例 44.9。多功能鎖boost::unique_lock

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
boost::timed_mutex mutex;
void thread1()
{
  using boost::this_thread::get_id;
  for (int i = 0; i < 5; ++i)
  {
    wait(1);
    boost::unique_lock<boost::timed_mutex> lock{mutex};
    std::cout << "Thread " << get_id() << ": " << i << std::endl;
    boost::timed_mutex *m = lock.release();
    m->unlock();
  }
}
void thread2()
{
  using boost::this_thread::get_id;
  for (int i = 0; i < 5; ++i)
  {
    wait(1);
    boost::unique_lock<boost::timed_mutex> lock{mutex,
      boost::try_to_lock};
    if (lock.owns_lock() || lock.try_lock_for(boost::chrono::seconds{1}))
    {
      std::cout << "Thread " << get_id() << ": " << i << std::endl;
    }
  }
}
int main()
{
  boost::thread t1{thread1};
  boost::thread t2{thread2};
  t1.join();
  t2.join();
}

Example44.9

示例 44.9 使用了 thread() 函數的兩個變體。兩種變體仍然在循環中將五個數字寫入標準輸出流,但它們現在使用類 boost::unique_lock 來鎖定互斥鎖。

thread1() 將變量 mutex 傳遞給 boost::unique_lock 的構造函數,這使得 boost::unique_lock 嘗試鎖定互斥鎖。在這種情況下,boost::unique_lock 的行為與 boost::lock_guard 沒有區別。 boost::unique_lock 的構造函數在互斥量上調用 lock()。

但是,boost::unique_lock 的析構函數不會釋放 thread1() 中的互斥量。在 thread1() 中,release() 在鎖上被調用,這將互斥體與鎖分離。默認情況下,boost::unique_lock 的析構函數會釋放一個互斥量,就像 boost::lock_guard 的析構函數一樣——但如果互斥量是解耦的則不會。這就是為什么在 thread1() 中顯式調用 unlock()。

thread2() 將 mutex 和 boost::try_to_lock 傳遞給 boost::unique_lock 的構造函數。這使得 boost::unique_lock 的構造函數不是在互斥體上調用 lock(),而是調用 try_lock()。因此,構造函數只嘗試鎖定互斥量。如果互斥量由另一個線程擁有,則嘗試失敗。

owns_lock() 可讓您檢測 boost::unique_lock 是否能夠鎖定互斥體。如果 owns_lock() 返回 true,thread2() 可以立即訪問 std::cout。如果 owns_lock() 返回 false,則調用 try_lock_for()。此成員函數也嘗試鎖定互斥鎖,但它會在失敗前等待互斥鎖一段指定的時間。在示例 44.9 中,鎖會嘗試一秒鐘來獲取互斥量。如果 try_lock_for() 返回 true,則可以訪問 std::cout。否則,thread2() 放棄并跳過一個數字。因此,示例中的第二個線程可能不會將五個數字寫入標準輸出流。

請注意,在示例 44.9 中,互斥量的類型是 boost::timed_mutex,而不是 boost::mutex。該示例使用 boost::timed_mutex,因為此互斥量是唯一提供成員函數 try_lock_for() 的互斥量。當對鎖調用 try_lock_for() 時調用此成員函數。 boost::mutex 僅提供成員函數 lock() 和 try_lock()。

boost::unique_lock 是一個獨占鎖。獨占鎖始終是互斥量的唯一所有者。另一個鎖只有在排他鎖釋放后才能獲得互斥鎖的控制權。 Boost.Thread 還支持類 boost::shared_lock 的共享鎖,它與 shared_mutex 一起使用。

示例 44.10。與 boost::shared_lock 共享鎖

#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
void wait(int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds{seconds});
}
boost::shared_mutex mutex;
std::vector<int> random_numbers;
void fill()
{
  std::srand(static_cast<unsigned int>(std::time(0)));
  for (int i = 0; i < 3; ++i)
  {
    boost::unique_lock<boost::shared_mutex> lock{mutex};
    random_numbers.push_back(std::rand());
    lock.unlock();
    wait(1);
  }
}
void print()
{
  for (int i = 0; i < 3; ++i)
  {
    wait(1);
    boost::shared_lock<boost::shared_mutex> lock{mutex};
    std::cout << random_numbers.back() << '\n';
  }
}
int sum = 0;
void count()
{
  for (int i = 0; i < 3; ++i)
  {
    wait(1);
    boost::shared_lock<boost::shared_mutex> lock{mutex};
    sum += random_numbers.back();
  }
}
int main()
{
  boost::thread t1{fill}, t2{print}, t3{count};
  t1.join();
  t2.join();
  t3.join();
  std::cout << "Sum: " << sum << '\n';
}

如果線程只需要對特定資源進行只讀訪問,則可以使用類型為 boost::shared_lock 的非獨占鎖。修改資源的線程需要寫訪問權,因此需要獨占鎖。由于具有只讀訪問權限的線程不受同時讀取同一資源的其他線程的影響,因此它可以使用非排他鎖并共享互斥鎖。

在示例 44.10 中,print() 和 count() 都只讀取變量 random_numbers。 print() 函數將 random_numbers 中的最后一個值寫入標準輸出流,count() 函數將其添加到變量 sum 中。因為兩個函數都不修改 random_numbers,所以它們都可以使用類型為 boost::shared_lock 的非獨占鎖同時訪問它。

在 fill() 函數內部,需要一個類型為 boost::unique_lock 的獨占鎖,因為它將新的隨機數插入到 random_numbers 中。 fill() 使用 unlock() 成員函數釋放互斥鎖,然后等待一秒鐘。與前面的示例不同,wait() 在 for 循環的末尾調用,以保證在 print() 或 count() 訪問容器之前至少將一個隨機數放入容器中。這兩個函數都在它們的 for 循環開始時調用 wait() 函數。

查看從不同位置對 wait() 函數的單獨調用,一個潛在問題變得明顯:函數調用的順序直接受到 CPU 實際執行各個線程的順序的影響。使用條件變量,可以同步各個線程,以便添加到 random_numbers 的值立即由不同的線程處理。

示例 44.11。帶有 boost::condition_variable_any 的條件變量

#include <boost/thread.hpp>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
boost::mutex mutex;
boost::condition_variable_any cond;
std::vector<int> random_numbers;
void fill()
{
  std::srand(static_cast<unsigned int>(std::time(0)));
  for (int i = 0; i < 3; ++i)
  {
    boost::unique_lock<boost::mutex> lock{mutex};
    random_numbers.push_back(std::rand());
    cond.notify_all();
    cond.wait(mutex);
  }
}
void print()
{
  std::size_t next_size = 1;
  for (int i = 0; i < 3; ++i)
  {
    boost::unique_lock<boost::mutex> lock{mutex};
    while (random_numbers.size() != next_size)
      cond.wait(mutex);
    std::cout << random_numbers.back() << '\n';
    ++next_size;
    cond.notify_all();
  }
}
int main()
{
  boost::thread t1{fill};
  boost::thread t2{print};
  t1.join();
  t2.join();
}

Example44.11

示例 44.11 刪除了 wait() 和 count() 函數。線程不再在每次迭代中等待一秒鐘;相反,它們會盡可能快地執行。此外,不計算總數;數字只是寫入標準輸出流。

為了確保隨機數的正確處理,各個線程使用條件變量進行同步,可以檢查多個線程之間的某些條件。

和以前一樣,fill() 函數在每次迭代時生成一個隨機數,并將其放入 random_numbers 容器中。為了阻止其他線程同時訪問容器,使用了排他鎖。這個例子沒有等待一秒鐘,而是使用了一個條件變量。調用 notify_all() 將喚醒一直在使用 wait() 等待此通知的每個線程。

查看 print() 函數的 for 循環,您可以看到為相同的條件變量調用了成員函數 wait()。當線程被調用 notify_all() 喚醒時,它會嘗試獲取互斥鎖,只有在 fill() 函數中成功釋放互斥鎖后才會成功。

這里的技巧是調用 wait() 也會釋放作為參數傳遞的互斥體。調用 notify_all() 后,fill() 函數通過調用 wait() 釋放互斥量。然后它會阻塞并等待其他線程調用 notify_all(),一旦隨機數被寫入標準輸出流,它就會在 print() 函數中發生。

請注意,對 print() 函數內的 wait() 成員函數的調用實際上發生在單獨的 while 循環中。這樣做是為了處理在 print() 中首次調用 wait() 成員函數之前已經將隨機數放入容器中的情況。通過將 random_numbers 中存儲的元素數量與預期的元素數量進行比較,成功處理了這種情況,并將隨機數寫入標準輸出流。

如果鎖不是在 for 循環中的本地鎖而是在外部作用域中實例化,則示例 44.11 也適用。事實上,這更有意義,因為不需要在每次迭代中都銷毀和重新創建鎖。由于互斥量總是通過 wait() 釋放,因此您無需在迭代結束時銷毀鎖。

原文鏈接:https://yamagota.blog.csdn.net/article/details/127896668

欄目分類
最近更新