2016年7月29日 星期五

Protocol Buffer for Python 教学

这篇文章会介绍

一:如何定义proto讯息格式
二:使用protocol buffer编译器
三:视同protocol buffer读写信息

为什么要使用protocol buffer?

这个例子使用一个非常简单的联络薄,允许读写。每笔资料代表一个人的名字,ID,邮箱地址及电话号码。

要如何把这些资料序列化及读取?我们有以下几个方法:

使用Python Pickle。这是Python预设的方法,可是这个方法不能有效处理Schema改动,而且要与C++及Java程序交换资料很难。

发明一个方法把资料变成字串,举例,4个整数可以变成“12:3”-23:67“。虽然这歌方法不能提供一次性编码及解析代码,而且解析需要一点运行成本,可是这个方法很简单有弹性,适合一些很简单的资料。

把资料序列化为XML,这很吸引因为XML是人类可读的,而且很多不同语言都支持。如果你要把资料分享给其他应用,XML是一个很好的选择。可是,XML空间要求巨大,早就臭名远播,而且编码对性能的负面影响很,XML DOM树明显比简单的读取一个class field来的复杂很多。

Protocol Buffer刚好可以解决这个问题。有了Protocol buffer,我们可以把资料结构写进一个proto描述档案,使用protocol buffer编译器从这个proto档产生class档案并且拥有一个高效率二进制而自动产生出来的编码及解析代码。产生出来的class提供getter及setter,处理读写细节。更重要的是,protocol buffer格式支持随着时间的格式延伸,代码可以读取使用就格式编码的资料。

定义你的协议格式

要建立一个联络薄应用,首先要从proto档案开始,首先加为每个资料结构加入一个message,然后为每个属性建立一个名字跟形态。看以下例子。

package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}

可以看到,文法跟C++及Java很像似。详细解释如下。

首先,一开始写一个package宣告,用来避免将来有可能出现的名字冲突。在Python里面,packages一般用资料夹结构去决定,所以在这个proto档案里使用package并不会影响产生出来的代码。可是,你还是应该宣告一个package去避免在各个protocol buffer名字空间的名字冲突以及避免Python意外的语言的名字冲突。

然后,进入messsage定义部分。一个message集合了一堆有形态的属性。很多的简单形态都可以使用,包括bool,int32,float,double,string。你也可以使用自定义形态。上面的例子Person就包含了PhoneNumber这个形态。你也可以定义enum形态去限制PhoneNumber的type为MOBILE,HOME,WORK其中之一。

你看到的“=1”,“=2”标记是用来识别独一无二的tag并用于二进制编码里面。tag号码1-15比正常的编码使用省1字节,因此如果要要优化的话我们可以决定把常用或者重复的元素予以1-15的tag,而16以上的tag留给不常见可有可无的元素。每个重复的元素要求把tag号码重新编码,所以重复的属性特别适合使用这个优化方法。

每个属性必须要用一下其中一个方法去描述一下:

required:必须提供一个数值给这个属性,否则这个message会被认为是未初始化。序列化一个未初始化的message会引起一个例外。解析一个未初始化的message会失败。除此以外,required属性跟optional属性一模一样。

optional:这个属性可以设定也可以不。如果没设定的话,预设值会被使用。简单的类型,我们可以指明预设值。举上例,phone number type预设使用HOME。如果没有指明预设值,系统预设值会被使用:数值类型用0,字串类型用空字串,布朗值用否。使用embedded messages的话,预设值永远是message的“default instance“或者”prototype“并且不会设置任何一个属性。如果用accessor向一个为设定的属性取值的话,会返回那个属性的预设值。

repeated:这个属性可能会重复0次或多次,就是可有可无的意思。重复的数值的顺序会被保留。可以把重复属性想象成动态大小的数列。

Required代表永远
使用required的时候要特别小心,如果某天打算停止写入及读取某个required属性,想把它从required改成optional将会产生很大问题。旧的读取者会认为缺少这个属性的message为不完整并且有可能漫无目的地拒绝或者扔掉这些messages。我们应该考虑使用特别为应用建立的检查有效性的代码取而代之。Google某些工程师得出以下结论:使用required斃多于利,他们倾向只使用optional及repeated。可是这个观点并不能一概而论。

把protocol buffer编译
看完proto档案怎么编写,现在可以编译了。首先你要看一大堆文章把protoc编译器安装好。直到有一天你在command line可以顺利跑protoc --version,你便算是安装好编译器了。
复杂点:protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto
简单点:protoc --python_out=. addressbook.proto
然后就会看见产生出来的档案addressbook_pb2.py

Protocol Buffer API
与C++及Java的代码产生不同,Python protocol buffer编译器不会为你直接产生资料提取代码,却会为所有messages,enums,fields及某些神秘的空类产生特别的描述器。
class Person(message.Message):
  __metaclass__ = reflection.GeneratedProtocolMessageType

  class PhoneNumber(message.Message):
    __metaclass__ = reflection.GeneratedProtocolMessageType
    DESCRIPTOR = _PERSON_PHONENUMBER
  DESCRIPTOR = _PERSON

class AddressBook(message.Message):
  __metaclass__ = reflection.GeneratedProtocolMessageType
  DESCRIPTOR = _ADDRESSBOOK
其中最重要的一行__metaclass__ = reflection.GeneratedProtocolMessageType详细如何工作不再此述,我们可以把它暂时把它想象成一个建立类的模板。在载入的时候,GeneratedProtocolMessageType元类视同特定的描述器为所有我们需要用到的讯息的产生相应的Python方法及相关的类。
你可以使用Person类犹如每个field已经定义在Message一样,例如
import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "jdoe@example.com"
phone = person.phone.add()
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.HOME
留意以上这些assingments并不是单纯把一些随意的field加到generic Python物件里面的。如果你尝试assign一些还没有在proto档定义的field,系统会抛出AttributeError。如果assign错type,系统会抛出TypeError。读取未设定的field将会返回预设值。
person.no_such_field = 1  # raises AttributeError
person.id = "1234"        # raises TypeError
Enums
addressbook_pb2.Person.WOKR拥有2的值。

Standard Message Methods
每个讯息类别都有一堆方法让你检查及操作这个类的。
IsInitialized():检查是不是所有fields都已经赋值了
__str__():返回human-readable的字串,侦错时特有效,例如str(message) or pirnt(message)
CopyFrom(other_msg):覆盖当前讯息
Clear():清空为空的状态
这几个方法都是implement了class Message的界面。

Parsing and Serialization
每个protocol buffer class都有使用protocol buffer binary format方法去读写。
SerializeToString():讯息变字串。留意那些字节都是binary而不是text。我们仅仅用str type作为一个方便的container
ParseFromString():string变讯息

Protocol Buffers and OO Design
Protocol Buffer class 仅仅是资料类别,不要当成object model。如果想要更丰富的behaviour,最好的方法是wrap掉protocol buffer class成为applicationg-specific class。如果对于proto档案没有控制权的开发者,warpping也是很好的方法,因为我们可以隐藏某些不扼要的资料跟方法,并且暴露有用的方法。记住,永远不要用继承产生出来的class去增加或者改变class的行为,这样会打破内部机制。

Writing A Message
以下例子首先从档案读取AddressBook,添加一个Person,把新的AddressBook写进档案。黄色的是跟protocol compiler产生出来的代码有关的。
#! /usr/bin/python

import addressbook_pb2
import sys

# This function fills in a Person message based on user input.
def PromptForAddress(person):
  person.id = int(raw_input("Enter person ID number: "))
  person.name = raw_input("Enter name: ")

  email = raw_input("Enter email address (blank for none): ")
  if email != "":
    person.email = email

  while True:
    number = raw_input("Enter a phone number (or leave blank to finish): ")
    if number == "":
      break

    phone_number = person.phone.add()
    phone_number.number = number

    type = raw_input("Is this a mobile, home, or work phone? ")
    if type == "mobile":
      phone_number.type = addressbook_pb2.Person.MOBILE
    elif type == "home":
      phone_number.type = addressbook_pb2.Person.HOME
    elif type == "work":
      phone_number.type = addressbook_pb2.Person.WORK
    else:
      print "Unknown phone type; leaving as default value."

# Main procedure:  Reads the entire address book from a file,
#   adds one person based on user input, then writes it back out to the same
#   file.
if len(sys.argv) != 2:
  print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
  sys.exit(-1)

address_book = addressbook_pb2.AddressBook()

# Read the existing address book.
try:
  f = open(sys.argv[1], "rb")
  address_book.ParseFromString(f.read())
  f.close()
except IOError:
  print sys.argv[1] + ": Could not open file.  Creating a new one."

# Add an address.
PromptForAddress(address_book.person.add())

# Write the new address book back to disk.
f = open(sys.argv[1], "wb")
f.write(address_book.SerializeToString())
f.close()
Extending a Protocol Buffer
往后当我们需要改进protocol buffer定义时候,如果需要让新buffer往前兼容并且让旧buffer往前兼容,有些规矩需要遵守的。
1. 一定不可以更改现有的任何tag number
2. 一定不可以加减任何required fields
3. 可以删除optional或者repeated fields
4. 可以增加新的optional或者repeated fields但是一定要用全新的tag numbers, 全新的意思是那些永远未被使用过的tag numbers,连删除了的fields也不曾使用过的

如果乖乖的遵守以上规条,旧代码会快乐的读取新讯息以及忽略新fields,因为对于旧代码来说,删除了的optional fields会有default values,而删除了的repeated fields会变成empty。新代码会读取就信息。可是,旧讯息一定不会存在任何新的optional fields,所以,要嘛用has_故意检查他们是否已经设定好,要嘛用[default=value]去提供一个合理的预设值。如果optional fields没有提供预设值,系统会给一个:空,否,零。如果新加了repeated field,新代码不能判断到底是新代码设定为空的,还是就代码没有设定,因为repeated field没有 has_ field。


2016年7月27日 星期三

Install Protocol Buffers for Python on CentOS 6.7 and Windows 7


Install protoc on CentOS 6.7

Clone the repo
git clone https://github.com/google/protobuf.git

Below is to follow here
https://github.com/google/protobuf/blob/master/src/README.md

Install tools
sudo yum install -y autoconf automake libtool curl make g++ unzip

Generate configure script
cd protobuf
./autogen.sh

Build C++ runtime and protoc
./configure --prefix=/usr
make
make check
sudo make install
sudo ldconfig # refresh shared library cache.

Test
protoc --version
libprotoc 3.0.0


Install Protocol Buffers for Python on Centos 6.7

Download and install it
wget https://github.com/google/protobuf/releases/download/v3.0.0-beta-4/protobuf-python-3.0.0-beta-4.tar.gz
tar -zxvf protobuf-python-3.0.0-beta-4.tar.gz
cd protobuf-3.0.0-beta-4/
cd python
python setup.py build
python setup.py test
python setup.py install (as root)


Install protoc.exe on Windows 7
Extract and place protoc.exe to C:\Windows\

Test
protoc --version
libprotoc 3.0.0

Install Protocol Buffers for Python on Windows 7

Protobuf for Python
Extract, go to ./python folder
python setup.py build
python setup.py test
python setup.py install (as root)


Test Protocol Buffers for Python on CentOS 6.7 and Windows 7

Create proto file
Create a file called Order.proto with following content
syntax="proto2";
message Order
{
  required int32 time = 1;
  required int32 userid = 2;
  required float price = 3;
  optional string desc = 4;
}

Generate .py file
protoc --python_out=. Order.proto

Create main.py
Create a file called main.py with following content
from Order_pb2 import *
if __name__=='__main__':
    order = Order()
    order.time = 123
    order.userid = 456
    order.price = 78.9
    order.desc = "here"
    print ('order', order)
    print ('time', order.time)
    print ('userid', order.userid)
    print ('price', order.price)
    print ('desc', order.desc)

Run main.py
D:\Lab\ProtocolBuffers>python main.py
order time: 123
userid: 456
price: 78.9
desc: "here"

time 123
userid 456
price 78.9
desc here

2016年7月15日 星期五

SQLite Database Browser on CentOS6

http://blog.thememoryleak.net/2013/02/sqlite-database-browser-on-centos6.html

SQLite Database Browser on CentOS6

Working through Michael Hartl's Ruby on Rails Tutorial, chapter 6 calls for the SQLite Database Browser.

Just wanted to put a note out there for anyone building this on CentOS6: make sure you check the version of qt that your qmake command runs against.

The below commands show how to check which version you're using, and run the correct one to build Sqlite Database Browser.

check installed qt versions
[user@dev-workstation sqlitebrowser]$ which qmake
/usr/lib64/qt-3.3/bin/qmake
[user@dev-workstation sqlitebrowser]$ ls /usr/lib64/qt*
/usr/lib64/qt-3.3:
bin  include  lib  mkspecs  phrasebooks  plugins  translations

/usr/lib64/qt4:
bin  mkspecs  phrasebooks  plugins  q3porting.xml

confirm qt4/bin/qmake is there
[user@dev-workstation sqlitebrowser]$ ls /usr/lib64/qt4/bin/qmake
/usr/lib64/qt4/bin/qmake

commands to run to build Sqlite Database Browser
[user@dev-workstation sqlitebrowser]$ /usr/lib64/qt4/bin/qmake
[user@dev-workstation sqlitebrowser]$ make

after building, move to your ~/bin folder and enjoy
[user@dev-workstation sqlitebrowser]$ cp sqlitebrowser ~/bin/
[user@dev-workstation sqlitebrowser]$ which sqlitebrowser
~/bin/sqlitebrowser
[user@dev-workstation sqlitebrowser]$ sqlitebrowser &
[user@dev-workstation sqlitebrowser]$

hope this helps. :)

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_lockdefer_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_locktry_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 并发编程的博客,希望感兴趣的同学继续关注 ;-)

2023 Promox on Morefine N6000 16GB 512GB

2023 Promox on Morefine N6000 16GB 512GB Software Etcher 100MB (not but can be rufus-4.3.exe 1.4MB) Proxmox VE 7.4 ISO Installer (1st ISO re...