2016年7月7日 星期四

C++11 并发指南三(Lock 详解)


http://www.cnblogs.com/haippy/p/3346477.html

这是我见过最好的C++ Lock讲解文章,比cplusplus.com写得更清楚



C++11 并发指南三(Lock 详解)

2013-10-02 11:38 by Haippy, 21055 阅读, 4 评论, 收藏, 编辑
在 《C++11 并发指南三(std::mutex 详解)》一文中我们主要介绍了 C++11 标准中的互斥量(Mutex),并简单介绍了一下两种锁类型。本节将详细介绍一下 C++11 标准的锁类型。
C++11 标准为我们提供了两种基本的锁类型,分别如下:
  • std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
  • std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
另外还提供了几个与锁类型相关的 Tag 类,分别如下:
  • std::adopt_lock_t,一个空的标记类,定义如下:
struct adopt_lock_t {};
 该类型的常量对象adopt_lock(adopt_lock 是一个常量对象,定义如下:
constexpr adopt_lock_t adopt_lock {};,// constexpr 是 C++11 中的新关键字)
通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。
  • std::defer_lock_t,一个空的标记类,定义如下:
struct defer_lock_t {};
 该类型的常量对象 defer_lock(defer_lock 是一个常量对象,定义如下:
constexpr defer_lock_t defer_lock {};,// constexpr 是 C++11 中的新关键字)
通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。
  • std::try_to_lock_t,一个空的标记类,定义如下:
struct try_to_lock_t {};
 该类型的常量对象 try_to_lock(try_to_lock 是一个常量对象,定义如下:
constexpr try_to_lock_t try_to_lock {};,// constexpr 是 C++11 中的新关键字)
通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。后面我们会详细介绍以上三种 Tag 类型在配合 lock_gurad 与 unique_lock 使用时的区别。

std::lock_guard 介绍

std::lock_gurad 是 C++11 中定义的模板类。定义如下:
template <class Mutex> class lock_guard;
lock_guard 对象通常用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁(注:类似 shared_ptr 等智能指针管理动态分配的内存资源 )。
模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型,标准库中定义几种基本的 BasicLockable 类型,分别 std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex (以上四种类型均已在上一篇博客中介绍)以及 std::unique_lock(本文后续会介绍 std::unique_lock)。(注:BasicLockable 类型的对象只需满足两种操作,lock 和 unlock,另外还有 Lockable 类型,在 BasicLockable 类型的基础上新增了 try_lock 操作,因此一个满足 Lockable 的对象应支持三种操作:lock,unlock 和 try_lock;最后还有一种 TimedLockable 对象,在 Lockable 类型的基础上又新增了 try_lock_for 和 try_lock_until 两种操作,因此一个满足 TimedLockable 的对象应支持五种操作:lock, unlock, try_lock, try_lock_for, try_lock_until)。
在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。

值得注意的是,lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁。

std::lock_guard 构造函数

lock_guard 构造函数如下表所示:
locking (1)
explicit lock_guard (mutex_type& m);
adopting (2)
lock_guard (mutex_type& m, adopt_lock_t tag);
copy [deleted](3)
lock_guard (const lock_guard&) = delete;
  1. locking 初始化
    • lock_guard 对象管理 Mutex 对象 m,并在构造时对 m 进行上锁(调用 m.lock())。
  2. adopting初始化
    • lock_guard 对象管理 Mutex 对象 m,与 locking 初始化(1) 不同的是, Mutex 对象 m 已被当前线程锁住。
  3. 拷贝构造
    • lock_guard 对象的拷贝构造和移动构造(move construction)均被禁用,因此 lock_guard 对象不可被拷贝构造或移动构造。
我们来看一个简单的例子(参考):
复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard, std::adopt_lock

std::mutex mtx;           // mutex for critical section

void print_thread_id (int id) {
  mtx.lock();
  std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
  std::cout << "thread #" << id << '\n';
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码
在 print_thread_id 中,我们首先对 mtx 进行上锁操作(mtx.lock();),然后用 mtx 对象构造一个 lock_guard 对象(std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);),注意此时 Tag 参数为 std::adopt_lock,表明当前线程已经获得了锁,此后 mtx 对象的解锁操作交由 lock_guard 对象 lck 来管理,在 lck 的生命周期结束之后,mtx 对象会自动解锁。
lock_guard 最大的特点就是安全易于使用,请看下面例子(参考),在异常抛出的时候通过 lock_guard 对象管理的 Mutex 可以得到正确地解锁。
复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard
#include <stdexcept>      // std::logic_error

std::mutex mtx;

void print_even (int x) {
  if (x%2==0) std::cout << x << " is even\n";
  else throw (std::logic_error("not even"));
}

void print_thread_id (int id) {
  try {
    // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
    std::lock_guard<std::mutex> lck (mtx);
    print_even(id);
  }
  catch (std::logic_error&) {
    std::cout << "[exception caught]\n";
  }
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码

std::unique_lock 介绍

但是 lock_guard 最大的缺点也是简单,没有给程序员提供足够的灵活度,因此,C++11 标准中定义了另外一个与 Mutex RAII 相关类 unique_lock,该类与 lock_guard 类相似,也很方便线程对互斥量上锁,但它提供了更好的上锁和解锁控制。
顾名思义,unique_lock 对象以独占所有权的方式( unique owership)管理 mutex 对象的上锁和解锁操作,所谓独占所有权,就是没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权。
在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的 unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作。

std::unique_lock 对象也能保证在其自身析构时它所管理的 Mutex 对象能够被正确地解锁(即使没有显式地调用 unlock 函数)。因此,和 lock_guard 一样,这也是一种简单而又安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。

值得注意的是,unique_lock 对象同样也不负责管理 Mutex 对象的生命周期,unique_lock 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 unique_lock 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 unique_lock 的生命周期结束之后,它所管理的锁对象会被解锁,这一点和 lock_guard 类似,但 unique_lock 给程序员提供了更多的自由,我会在下面的内容中给大家介绍 unique_lock 的用法。
另外,与 lock_guard 一样,模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型,标准库中定义几种基本的 BasicLockable 类型,分别 std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex (以上四种类型均已在上一篇博客中介绍)以及 std::unique_lock(本文后续会介绍 std::unique_lock)。(注:BasicLockable 类型的对象只需满足两种操作,lock 和 unlock,另外还有 Lockable 类型,在 BasicLockable 类型的基础上新增了 try_lock 操作,因此一个满足 Lockable 的对象应支持三种操作:lock,unlock 和 try_lock;最后还有一种 TimedLockable 对象,在 Lockable 类型的基础上又新增了 try_lock_for 和 try_lock_until 两种操作,因此一个满足 TimedLockable 的对象应支持五种操作:lock, unlock, try_lock, try_lock_for, try_lock_until)。

std::unique_lock 构造函数

std::unique_lock 的构造函数的数目相对来说比 std::lock_guard 多,其中一方面也是因为 std::unique_lock 更加灵活,从而在构造 std::unique_lock 对象时可以接受额外的参数。总地来说,std::unique_lock 构造函数如下:
default (1)
unique_lock() noexcept;
locking (2)
explicit unique_lock(mutex_type& m);
try-locking (3)
unique_lock(mutex_type& m, try_to_lock_t tag);
deferred (4)
unique_lock(mutex_type& m, defer_lock_t tag) noexcept;
adopting (5)
unique_lock(mutex_type& m, adopt_lock_t tag);
locking for (6)
template <class Rep, class Period>
unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
locking until (7)
template <class Clock, class Duration>
unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
copy [deleted] (8)
unique_lock(const unique_lock&) = delete;
move (9)
unique_lock(unique_lock&& x);
下面我们来分别介绍以上各个构造函数:
(1) 默认构造函数
新创建的 unique_lock 对象不管理任何 Mutex 对象。
(2) locking 初始化
新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞。
(3) try-locking 初始化
新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。
(4) deferred 初始化
新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象。
(5) adopting 初始化
新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。
(6) locking 一段时间(duration)
新创建的 unique_lock 对象管理 Mutex 对象 m,并试图通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。
(7) locking 直到某个时间点(time point)
新创建的 unique_lock 对象管理 Mutex 对象m,并试图通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。
(8) 拷贝构造 [被禁用]
unique_lock 对象不能被拷贝构造。
(9) 移动(move)构造
新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。
综上所述,由 (2) 和 (5) 创建的 unique_lock 对象通常拥有 Mutex 对象的锁。而通过 (1) 和 (4) 创建的则不会拥有锁。通过 (3),(6) 和 (7) 创建的 unique_lock 对象,则在 lock 成功时获得锁。
关于unique_lock 的构造函数,请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock, std::unique_lock
                          // std::adopt_lock, std::defer_lock
std::mutex foo,bar;

void task_a () {
  std::lock (foo,bar);         // simultaneous lock (prevents deadlock)
  std::unique_lock<std::mutex> lck1 (foo,std::adopt_lock);
  std::unique_lock<std::mutex> lck2 (bar,std::adopt_lock);
  std::cout << "task a\n";
  // (unlocked automatically on destruction of lck1 and lck2)
}

void task_b () {
  // foo.lock(); bar.lock(); // replaced by:
  std::unique_lock<std::mutex> lck1, lck2;
  lck1 = std::unique_lock<std::mutex>(bar,std::defer_lock);
  lck2 = std::unique_lock<std::mutex>(foo,std::defer_lock);
  std::lock (lck1,lck2);       // simultaneous lock (prevents deadlock)
  std::cout << "task b\n";
  // (unlocked automatically on destruction of lck1 and lck2)
}


int main ()
{
  std::thread th1 (task_a);
  std::thread th2 (task_b);

  th1.join();
  th2.join();

  return 0;
}
复制代码

std::unique_lock 移动(move assign)赋值操作

std::unique_lock 支持移动赋值(move assignment),但是普通的赋值被禁用了,
move (1)
unique_lock& operator= (unique_lock&& x) noexcept;
copy [deleted] (2)
unique_lock& operator= (const unique_lock&) = delete;
移动赋值(move assignment)之后,由 x 所管理的 Mutex 对象及其状态将会被新的 std::unique_lock 对象取代。

如果被赋值的对象之前已经获得了它所管理的 Mutex 对象的锁,则在移动赋值(move assignment)之前会调用 unlock 函数释放它所占有的锁。

调用移动赋值(move assignment)之后, x 对象如同通过默认构造函数所创建的,也就不再管理任何 Mutex 对象了。请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;           // mutex for critical section

void print_fifty (char c) {
  std::unique_lock<std::mutex> lck;         // default-constructed
  lck = std::unique_lock<std::mutex>(mtx);  // move-assigned
  for (int i=0; i<50; ++i) { std::cout << c; }
  std::cout << '\n';
}

int main ()
{
  std::thread th1 (print_fifty,'*');
  std::thread th2 (print_fifty,'$');

  th1.join();
  th2.join();

  return 0;
}
复制代码

std::unique_lock 主要成员函数

本节我们来看看 std::unique_lock 的主要成员函数。由于 std::unique_lock 比 std::lock_guard 操作灵活,因此它提供了更多成员函数。具体分类如下:
  1. 上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until 和 unlock
  2. 修改操作:移动赋值(move assignment)(前面已经介绍过了),交换(swap)(与另一个 std::unique_lock 对象交换它们所管理的 Mutex 对象的所有权),释放(release)(返回指向它所管理的 Mutex 对象的指针,并释放所有权)
  3. 获取属性操作:owns_lock(返回当前 std::unique_lock 对象是否获得了锁)、operator bool()(与 owns_lock 功能相同,返回当前 std::unique_lock 对象是否获得了锁)、mutex(返回当前 std::unique_lock 对象所管理的 Mutex 对象的指针)。
std::unique_lock::lock请看下面例子(参考):
上锁操作,调用它所管理的 Mutex 对象的 lock 函数。如果在调用  Mutex 对象的 lock 函数时该 Mutex 对象已被另一线程锁住,则当前线程会被阻塞,直到它获得了锁。
该函数返回时,当前的 unique_lock 对象便拥有了它所管理的 Mutex 对象的锁。如果上锁操作失败,则抛出 system_error 异常。
复制代码
// unique_lock::lock/unlock
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock

std::mutex mtx;           // mutex for critical section

void print_thread_id (int id) {
  std::unique_lock<std::mutex> lck (mtx,std::defer_lock);
  // critical section (exclusive access to std::cout signaled by locking lck):
  lck.lock();
  std::cout << "thread #" << id << '\n';
  lck.unlock();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码
std::unique_lock::try_lock
上锁操作,调用它所管理的 Mutex 对象的 try_lock 函数,如果上锁成功,则返回 true,否则返回 false。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock

std::mutex mtx;           // mutex for critical section

void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::defer_lock);
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck.try_lock())
    std::cout << '*';
  else                    
    std::cout << 'x';
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);

  for (auto& x: threads) x.join();

  return 0;
}
复制代码
std::unique_lock::try_lock_for
上锁操作,调用它所管理的 Mutex 对象的 try_lock_for 函数,如果上锁成功,则返回 true,否则返回 false。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex, std::unique_lock, std::defer_lock

std::timed_mutex mtx;

void fireworks () {
  std::unique_lock<std::timed_mutex> lck(mtx,std::defer_lock);
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!lck.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\n";
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码
std::unique_lock::try_lock_until
上锁操作,调用它所管理的 Mutex 对象的 try_lock_for 函数,如果上锁成功,则返回 true,否则返回 false。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex, std::unique_lock, std::defer_lock

std::timed_mutex mtx;

void fireworks () {
  std::unique_lock<std::timed_mutex> lck(mtx,std::defer_lock);
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!lck.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\n";
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码
std::unique_lock::unlock
解锁操作,调用它所管理的 Mutex 对象的 unlock 函数。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock

std::mutex mtx;           // mutex for critical section

void print_thread_id (int id) {
  std::unique_lock<std::mutex> lck (mtx,std::defer_lock);
  // critical section (exclusive access to std::cout signaled by locking lck):
  lck.lock();
  std::cout << "thread #" << id << '\n';
  lck.unlock();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码
std::unique_lock::release
返回指向它所管理的 Mutex 对象的指针,并释放所有权。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;
int count = 0;

void print_count_and_unlock (std::mutex* p_mtx) {
  std::cout << "count: " << count << '\n';
  p_mtx->unlock();
}

void task() {
  std::unique_lock<std::mutex> lck(mtx);
  ++count;
  print_count_and_unlock(lck.release());
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<10; ++i)
    threads.emplace_back(task);

  for (auto& x: threads) x.join();

  return 0;
}
复制代码
std::unique_lock::owns_lock
返回当前 std::unique_lock 对象是否获得了锁。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::try_to_lock

std::mutex mtx;           // mutex for critical section

void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::try_to_lock);
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck.owns_lock())
    std::cout << '*';
  else                    
    std::cout << 'x';
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);

  for (auto& x: threads) x.join();

  return 0;
}
复制代码
std::unique_lock::operator bool()
与 owns_lock 功能相同,返回当前 std::unique_lock 对象是否获得了锁。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::try_to_lock

std::mutex mtx;           // mutex for critical section

void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::try_to_lock);
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck)
    std::cout << '*';
  else                    
    std::cout << 'x';
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);

  for (auto& x: threads) x.join();

  return 0;
}
复制代码
std::unique_lock::mutex
返回当前 std::unique_lock 对象所管理的 Mutex 对象的指针。
请看下面例子(参考):
复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock

class MyMutex : public std::mutex {
  int _id;
public:
  MyMutex (int id) : _id(id) {}
  int id() {return _id;}
};

MyMutex mtx (101);

void print_ids (int id) {
  std::unique_lock<MyMutex> lck (mtx);
  std::cout << "thread #" << id << " locked mutex " << lck.mutex()->id() << '\n';
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_ids,i+1);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码
好了,本文先介绍到这里,我们基本上介绍完了 C++11 多线程编程中两种最基本的锁类型,后面我会继续更新有关 C++11 并发编程的博客,希望感兴趣的同学继续关注 ;-)

2016年4月29日 星期五

最簡單的 CMake 使用說明 – how to use cmake

http://mqjing.blogspot.hk/2009/09/cmake-how-to-use-cmake.html
最簡單的 CMake 使用說明 – how to use cmake


2009年9月15日 星期二


[程式設計] 最簡單的 CMake 使用說明 – how to use cmake

http://mqjing.blogspot.hk/2009/09/cmake-how-to-use-cmake.html
我們常常改別人已經寫好的 open source 專案, 讓我有一種感覺 … 似乎大的系統都會有一套 building system, 先解析平台上的 toolchain, 使得系統可以容易安裝到其他平台上.
在 Unix 世界裡面, 最常見的就是 GNU Building System. 也就是你常見的先下 .configure -參數 指令, 然後會產生一堆 Makefile. 使用 building system 有另外一個好處, 那就是我們的 source code 就不會被某個編譯環境鎖住了 (ex: Visual Studio .Net).

今天要介紹的是 CMake!
CMake 指令, 可以讓我們的使用者更容易把程式建構於不同的平台. 重要的是, 它可以讓我選擇要產生 Makefile 或 Visual Studio .Net 的專案檔 (sln 專案檔).
本文的主要結構為:
        1. 最簡單的 CMakeLists.txt 範例
        2. 如何在 Cygwin 下, 自動產生 Makefile
        3. 如何在 Windows 下, 自動產生 Visual Studio .Net 專案檔 (sln)
        4. FAQ
               -  設定/使用變數範例: set       
               -  建立/使用 macro 範例: MACRO
               -  存取環境變數的範例: $ENV{PATH}
               -  加入 Post Build 的範例: add_custom_command
               -  秀 Message 的範例: Message
               -  若你的執行檔是 Windows 程式, 請加入 Win32
               -  MFC 設定
               -  設定 source code 群組範例: set_group
               -  目前 binary 目錄 與 source 目錄:
                              CMAKE_CURRENT_SOURCE_DIR
                              CMAKE_CURRENT_BINARY_DIR
             
   -  建立 dll 範例
                - console 與 windows (/SUBSYSTEM:CONSOLE or /SUBSYSTEM:WINDOWS)
       5.  好用的變數 (more)

我想達成的目的, 如下所示:
cmake_structure

先把範例端上來

-------------------- fun.cpp --------------------
int add(int a,int b){
    return a+b;
}

// fun.h
int add(int a,int b);

-------------------- main.cpp --------------------
#include "fun.h"
#include <stdio.h>
int main(){
    int ans=add(1,2);
    printf("ans=%d",ans);
    return 0;
}

我想要做的事情是
1. 建立一個 library 檔稱為 MyLibrary, 裡面包含 fun.cpp
2. 自動建立 makefile, 把 main.cpp 與 MyLibrary 連接並且編譯.
用 cmake 該怎麼作?

Step 1: 寫一個 CMakeLists.txt, 描述該怎麼編譯你的 source
ex:
-------------------- CMakeLists.txt -----------------
project(MyProject)
add_library(MyLibrary fun.cpp)  # 建立程式庫 MyLibrary
add_executable(MyProgram main.cpp) # 建立執行檔 MyProgram

# 產生執行檔 MyProgram 與 程式庫 MyLibrary 連結指令
target_link_libraries(MyProgram MyLibrary)
-------------------- end of CMakeLists.txt -----------------

Step 2: 把它與你的 source files 放在一起
ex:
cmake_CMakeLists
Step 3: Enjoy

選擇你要產生的專案

自動產生 Makefile 的作法


Step 1: 在要放置 binary 的位置, 直接下指令 cmake [source code 目錄位置]
ex: 我在 ~/cmake_demo/bin 這個空目錄中, 下 cmake ../source 指令
cmake
 Step 2: 來看看結果如何? (cmake 會把 Makefile 放在 bin/ 中)
01_Makefile

 Step 3: 在 bin/ 下 make 指令, 看到底會不會動
02_in_process

嗯! 確實可以執行! ^_^
02_result

----------------------------------------------------------------------------------------

產生 Microsoft Visual Studio 專案的作法


Step 1: 選擇編譯結果目錄, 我們希望
    a.  指定 source code 的位置在: cmake_demo/source
    b. 編譯後的結果放在: cmake_demo/bin
    這樣作就不會把 source code 目錄弄髒了.
04_Windows_Configure

Step 2: 選擇產生 Visual Studio 8 2005 專案檔
05_win_Choose_Target_and_Specify_Native_compilers

Step 3: 設定編譯器所在位置
    ex: C:\Program Files\Microsoft Visual Studio 8\VC\bin
06_win_Choose_VisualStudio_cl

Step 4: 選擇 [Generate] 開始產生 Visual Studio 專案
07_win_reConfigure_and_runGenerate
07_win_run_testing

08_win_solution_result

FAQ
Unicode 設定的問題
如果你的字元處理是 Unicode, 那麼可以在 CMakeLists.txt 裡面加入
# 設定 Unicode
add_definitions(-DUNICODE -D_UNICODE)
如下圖所示
unicode

你會在產生出來的 project 設定中, 看見
unicode_prj 
# 設定變數範例           set(MYLIBPATH "C:\\Documents and
                  Settings\\Jing\\Desktop\\opencv_bin\\lib\\release\\")
# 使用變數範例           target_link_libraries(${name} ${MYLIBPATH}cv200.lib)

# 建立 macro 範例
           MACRO(MY_DEFINE_EXAMPLE name srcs)
                 add_executable(${name} ${srcs})
           ENDMACRO(MY_DEFINE_EXAMPLE)
# 使用 macro 範例            MY_DEFINE_EXAMPLE(demo     demo.cpp)

# 存取環境變數的範例
             MESSAGE("$ENV{PATH}")
# 加入 Post Build 的範例     add_custom_command(
            TARGET ${MyPluginTarget}
            POST_BUILD
            COMMAND copy
                           ${CMAKE_CFG_INTDIR}\\${MyPluginTarget}.dll
                           "C:\\Program Files\\Mozilla Firefox\\
                             plugins\\${MyPluginTarget}.dll"
        )

# 若你的執行檔是 Windows 程式, 請加入 Win32
# ex:
        add_executable(demo WIN32 ${SOURCE})

# MFC 設定
# ex:
        set(CMAKE_MFC_FLAG 2)
        set_target_properties(MyApp      PROPERTIES   
                   COMPILE_DEFINITIONS
                                                                  _AFXDLL, _UNICODE, UNICODE,
                                                                  _BIND_TO_CURRENT_CRT_VERSION,
                                                                  _BIND_TO_CURRENT_MFC_VERSION
                    LINK_FLAGS
                                                                  " /ENTRY:\"wWinMainCRTStartup\"    ")
         記得:
                         add_executable(demo WIN32 ${SOURCE})
# 設定 source code 群組範例
ex 1:
    file(GLOB_RECURSE myRESOURCE_FILE “*.def” “*.rc”)
    source_group(Resource FILES ${myRESOURCE_FILE })
ex 2:
    source_group(abc_group REGULAR_EXPRESSION "abc.*")
    source_group(123_group REGULAR_EXPRESSION "123.*")
   
    set(SOURCE abc.cpp abc.h 123.cpp 123.h abc.rc main.cpp main.h
                              resource.h stdafx.cpp  stdafx.h
                              ./res/123.ico ./res/123.rc2)
     add_executable(demo WIN32 ${SOURCE})

# 建立 dll 範例
ex:
     add_library( target SHARED a.c b.c);

#console 與 windows (/SUBSYSTEM:CONSOLE or /SUBSYSTEM:WINDOWS)
a. 如果你的程式是 Windows.
          => 則 add_executable(demo WIN32 ${SOURCE})
b. 如果是 console, 則
          => 則 add_executable(demo ${SOURCE})


5. 好用的變數 (more)
PROJECT_SOURCE_DIR:  包含 project 命令的 CMakeFile.txt 完整路徑. (ex: c:\abc)
usage:
       set(PROJECT_HOME ${PROJECT_SOURCE_DIR}\\..\\)

Enjoy.
by Jing

2016年4月28日 星期四

PXE Boot on CentOS 7

http://www.unixmen.com/install-pxe-server-and-configure-pxe-client-on-centos-7/


Essentially you follow the article "Install PXE Server And Configure PXE Client On CentOS 7" of the link about to setup a PXE network.


A few thing to note.
1.
You may see below
curl: (22) The requested URL returned error: 403 Forbidden
Warning: Download 'http://192.16.1.150/centos7_x64/LiveOS/squashfs.img' failed!

So, using this will be working
[root@pxe centos7_x64]# vi /etc/httpd/conf.d/pxeboot.conf
Alias /centos7_x64 /var/lib/tftpboot/centos7_x64/
<Directory /var/lib/tftpboot/centos7_x64>
    Options Indexes FollowSymLinks
    AllowOverride none
    Require all granted
</Directory>
<Directory />
    Options Indexes FollowSymLinks
    AllowOverride none
    Require all granted
</Directory>

2. DCHP ARGS

vi /etc/sysconfig/dhcpd
# WARNING: This file is NOT used anymore.

# If you are here to restrict what interfaces should dhcpd listen on,
# be aware that dhcpd listens *only* on interfaces for which it finds subnet
# declaration in dhcpd.conf. It means that explicitly enumerating interfaces
# also on command line should not be required in most cases.

# If you still insist on adding some command line options,
# copy dhcpd.service from /lib/systemd/system to /etc/systemd/system and modify
# it there.
# https://fedoraproject.org/wiki/Systemd#How_do_I_customize_a_unit_file.2F_add_a_custom_unit_file.3F

DHCPDARGS=enp0s8

# example:
# $ cp /usr/lib/systemd/system/dhcpd.service /etc/systemd/system/
# $ vi /etc/systemd/system/dhcpd.service
# $ ExecStart=/usr/sbin/dhcpd -f -cf /etc/dhcp/dhcpd.conf -user dhcpd -group dhcpd --no-pid <your_interface_name(s)>
# $ systemctl --system daemon-reload
# $ systemctl restart dhcpd.service



3. When setting the dchp server, you may not be able to restarat the dhcpd service.
You need to add a subnet section accoridng to http://linux.vbird.org/linux_server/0340dhcp.php#theory

subnet 192.168.100.0 netmask 255.255.255.0 {
    range 192.168.100.101 192.168.100.200;  <==分配的 IP 範圍

    # 3. 關於固定的 IP 啊!
    host win7 {
        hardware ethernet    08:00:27:11:EB:C2; <==用戶端網卡 MAC
        fixed-address        192.168.100.30;    <==給予固定的 IP
    }
}

2016年4月22日 星期五

Thrift C++ on Linux with Makefile Tutorial


首先假设你已经安装好thrift,我用的是CentOS7.2

C++

[root@thrift gen-cpp]# thrift --version
Thrift version 1.0.0-dev

准备thrift档案内容如下
[root@thrift cal]# vi cal.thrift
struct nums {
    1: i32 i1;
    2: i32 i2;
}

service sumservice {
    void sum(1: nums n)
}

然后用thrift产生cpp的header及source档案
[root@thrift cal]# thrift --gen cpp cal.thrift

可以看见产生了gen-cpp文件夹,进入gen-cpp文件夹,看见以下几个产生出来的文件。
其中3组.h及.cpp文件:cal_constants.*,cal_types.*,sumservice.*,共6个文件thrift已经完全写好。
cal_constants.* 及 cal_types.* 的命名方式是基于cal.thrift档案名称的
sumservice.* 的命名方式cal.thrift档案里面所有的service的名称的
sumservice.*是server side交易资料的implementation
产生出来的3组.h及.cpp合共全部6个文件一般无需自行更改
[root@thrift sum]# ls && cd gen-cpp/ && ls
cal.thrift  gen-cpp
cal_constants.cpp  cal_types.cpp  sumservice.cpp  sumservice_server.skeleton.cpp
cal_constants.h    cal_types.h    sumservice.h

留意sumservice.*里面的几个class名字及顺序
If, IfFactory, IfSingletonFactory, Null:If
args__isset, args, pargs, result, presult
Client:If, Processor:TDispatchProcessor, ProcessorFactory:TPorcessorFactory
Multiface:If, ConcurrentClient : If, 

唯一需要自行更改的是sumservice_server.skeleton.cpp, 这是Handler:If的
原本这个sumservice的预设implementation是把function名字打印出来
[root@thrift gen-cpp]# vi sumservice_server.skeleton.cpp
class sumserviceHandler : virtual public sumserviceIf {
 public:
  sumserviceHandler() {
    // Your initialization goes here
  }
  void sum(const nums& n) {
    // Your implementation goes here
    printf("sum\n");
  }
};
其中的main function大意是这样的,主要有关联的是Handler>TProcessor>TSimpleServer,而ServerTransport, TransportFactory及ProtocolFactory是配角。
int main() {
  TSimpleServer server(TProcessor(sumserviceHandler()));
  server.server();
};

由于每次运行都会把这歌现有的skeleton覆盖掉,所以现在干脆直接改名以免后顾之忧,改名以后列出的档案也比较工整
[root@thrift gen-cpp]# mv sumservice_server.skeleton.cpp server.cpp && ls
cal_constants.cpp  cal_types.cpp  client.cpp  sumservice.cpp
cal_constants.h    cal_types.h    server.cpp  sumservice.h

我们把server.cpp其中的两个方法改成以下有意义的内容,值得注意的是这两个方法都是放在一个由sumserviceIf界面虚疑继承的
[root@thrift gen-cpp]# vi server.cpp
class sumserviceHandler : virtual public sumserviceIf {
sumserviceHandler() {
    printf("server created \n");
  }
void sum(const nums& n) {
    printf("sum: %d + %d = %d \n", n.i1, n.i2, n.i1 + n.i2);
  }
}

用g++编译server程序 其中server档案需要参考types, constants 及service档案,留意最后要加上-lthrift
[root@thrift gen-cpp]# g++ -o server server.cpp sumservice.cpp cal_types.cpp cal_constants.cpp -lthrift

可以试试把server程序跑起来,并且看见sumserviceHandler的建构子被呼叫了
[root@thrift gen-cpp]# ls
cal_constants.cpp  cal_types.cpp  server          sumservice.h
cal_constants.h    cal_types.h    sumservice.cpp  server.cpp
[root@thrift gen-cpp]# ./server
server created
^C


下一步要建立client代码去跟server程序沟通
首先要从sumservice.h找出你等下准备要继承的client class, grep Client一下会发现名有两个classes sumserviceClient 及 sumserviceConcurrentClient 
[root@thrift gen-cpp]# grep Client sumservice.h
#include <thrift/async/TConcurrentClientSyncInfo.h>
class sumserviceClient : virtual public sumserviceIf {
  sumserviceClient(boost::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) {
  sumserviceClient(boost::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, boost::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) {
class sumserviceConcurrentClient : virtual public sumserviceIf {
  sumserviceConcurrentClient(boost::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) {
  sumserviceConcurrentClient(boost::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, boost::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) {
  ::apache::thrift::async::TConcurrentClientSyncInfo sync_;


client跟server的基本代码结构非常类似,所以直接考出来修改
[root@thrift gen-cpp]# cp server.cpp client.cpp && vi client.cpp

顶头加入TSocket.h并把main方法改成以下
#include <thrift/transport/TSocket.h>
int main(int argc, char **argv) {
  shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
  shared_ptr<TTransport> transport(new TBufferedTransport(socket));
  shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
  transport->open();
  // client code starts here
  nums n;
  n.i1 = 1;
  n.i2 = 2;
  sumserviceClient(protocol).sum(n);
  // client code ends here
  transport->close();
  return 0;
}

client.cpp以下几句只跟server有关却跟client无关,正在写client code的我们为求简洁把他们移除掉。不怕,万一忘记移除,client程序还是可以编译跑起来的。
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
using namespace ::apache::thrift::server;
class sumserviceHandler
shared_ptr<sumserviceHandler> handler(new sumserviceHandler());
shared_ptr<TProcessor> processor(new sumserviceProcessor(handler));
shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));

client.cpp中你会看见原本server的TTransportFactory及TProtocolFactory,如果把它们的Factory去掉就会变成client的要员
  1. boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));    
  2.     boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));    
  3.     boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); 

client.cpp整个就是这样
#include "sumservice.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TBufferTransports.h>
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using boost::shared_ptr;
#include <thrift/transport/TSocket.h>
int main(int argc, char **argv) {
  shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
  shared_ptr<TTransport> transport(new TBufferedTransport(socket));
  shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
  transport->open();
  // client code starts here
  nums n;
  n.i1 = 1;
  n.i2 = 2;
  sumserviceClient(protocol).sum(n);
  // client code ends here
  transport->close();
  return 0;
}


可以编译了
[root@thrift gen-cpp]# g++ -o client client.cpp sumservice.cpp cal_types.cpp cal_constants.cpp -lthrift

如果看见這个问题不要紧
[ricky@thrift1 gen-cpp]$ ./server
./server: error while loading shared libraries: libthrift-1.0.0-dev.so: cannot open shared object file: No such file or directory

再跑一次就可以
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
$ export LD_LIBRARY_PATH
$ ./server


可以跑了!背景跑server,前景跑client
[root@thrift gen-cpp]# ./server &
[1] 22189
[root@thrift gen-cpp]# server created
./client
sum: 1 + 2 = 3
[root@thrift gen-cpp]# pkill server


做个最原始的makefile,可以看到server跟client档案的dependency
[root@thrift gen-cpp]# vi makefile

.PHONY: all
all: server client

server: cal_constants.cpp cal_types.cpp sumservice.cpp cal_constants.h cal_types.h server.cpp sumservice.h
        g++ -std=c++11 -o server cal_constants.cpp cal_types.cpp sumservice.cpp server.cpp -lthrift
client: cal_constants.cpp cal_types.cpp client.cpp sumservice.cpp cal_constants.h cal_types.h server.cpp sumservice.h
        g++ -std=c++11 -o client cal_constants.cpp cal_types.cpp sumservice.cpp client.cpp -lthrift


Python

假设你不用C++而要使用Python,用thrift产生cpp的header及source档案
[root@thrift sum]# thrift --gen py cal.thrift && ls && ls ./gen-py/ && ls ./gen-py/cal
cal.thrift  gen-cpp  gen-py
cal  __init__.py
constants.py  __init__.py  sumservice.py  sumservice-remote  ttypes.py


可以看到thrift并不会把server代码skeleton产生给你,于是你需要在thrift档案同一目录下,建立以下的server.py,值得注意的是里面的CalHandler这个类并没有使用继承,只要是任何一个有写好sum(self,n)的类就可以送给sumservice.Processor()去被呼叫。留意,sumservice.py已经由thrift写好里面传送的代码,不用自己修改的。
[root@thrift sum]# vi server.py
#!/usr/bin/env python
import socket
import sys
sys.path.append('./gen-py')
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
from cal import sumservice
from cal.ttypes import *
class CalHandler :
    def sum(self, n) :
        print(n.i1 + n.i2)
handler = CalHandler()
processor = sumservice.Processor(handler)
transport = TSocket.TServerSocket("127.0.0.1", 9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)

server.serve()

参考./gen-py/sum/ttypes.py,可以看见nums已经写好了建构子,与c++的POD struct不同
class nums(object):
    def __init__(self, i1=None, i2=None,):
        self.i1=i1
        self.i2=i2

[root@thrift sum]# vi client.py
#!/usr/bin/env python
import sys
sys.path.append('./gen-py')
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from cal import sumservice
from cal.ttypes import *
try:
    transport = TSocket.TSocket('127.0.0.1', 9090)
    transport = TTransport.TBufferedTransport(transport)
    protocol = TBinaryProtocol.TBinaryProtocol(transport)
    client = sumservice.Client(protocol)
    transport.open()
    client.sum(nums(1,2)) # call constructor of nums() class from ttypes.py
    transport.close()
except Thrift.TException as ex:

    print ("%s" % (ex.message))

Java

must use "127.0.0.1" instead of "localhost" in server.py and JavaClient.java

JavaClient.java
import org.apache.thrift.TException;
import org.apache.thrift.transport.TSSLTransportFactory;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TSSLTransportFactory.TSSLTransportParameters;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
public class JavaClient {
    public static void main(String[] args) {
        try {
            TTransport transport;
            transport = new TSocket("127.0.0.1", 9090);
            transport.open();
            TProtocol protocol = new  TBinaryProtocol(transport);
            sumservice.Client client = new sumservice.Client(protocol);
            client.sum(new nums(1, 6));
            System.out.println("done");
            transport.close();
        }
        catch (TException x) {
            x.printStackTrace();
        }
    }
}

[ricky@thrift2 cal]$ thrift --gen java cal.thrift && ls gen-java
nums.java  sumservice.java

[ricky@thrift1 gen-java]$ pwd
/home/ricky/dev/Thrift/cal/gen-java
cp /path/to/thrift/lib/java/build/lib/*.jar ./lib/
cp /path/to/thrift/lib/java/build/libthrift-1.0.0.jar ./lib/

[ricky@thrift2 gen-java]$ javac -cp ".:./lib/*" JavaClient.java && java -cp ".:./lib/*" JavaClient






asdfas

2007 to 2023 HP and Dell Servers Comparison

  HP Gen5 to Gen11  using ChatGPT HP ProLiant Gen Active Years CPU Socket Popular HP CPUs Cores Base Clock Max RAM Capacity Comparable Dell ...