Oracle Solaris Studio 12.2:C++ 用户指南

10.4.1 MT 安全的 iostream 库的组织

MT 安全的 iostream 库的组织与其他版本的 iostream 库稍有不同。库的导出接口指的是 iostream 类的受保护的公共成员函数以及可用基类集合,这一点与其他版本的库相同;但类的分层结构是不同的。有关详细信息,请参见10.4.2 iostream 库接口更改

原来的核心类已重命名,即添加了前缀 unsafe_表 10–1 列出了属于 iostream 软件包核心的类。

表 10–1 原来的 iostream 核心类

类 

说明 

stream_MT

多线程安全类的基类。 

streambuf

缓冲区的基类。 

unsafe_ios

该类包含各种流类通用的状态变量;例如,错误和格式化状态。 

unsafe_istream

该类支持从 streambuf 检索的字符序列的有格式和无格式转换。

unsafe_ostream

该类支持存储到 streambuf 中的字符序列的有格式和无格式转换。

unsafe_iostream

该类合并 unsafe_istream 类和 unsafe_ostream 类,以便进行双向操作。

每个 MT 安全的类都是从基类 stream_MT 派生而来。每个 MT 安全的类(除了 streambuf 外)也都是从现有的 unsafe_ 基类派生而来。示例如下:


class streambuf: public stream_MT {...};
class ios: virtual public unsafe_ios, public stream_MT {...};
class istream: virtual public ios, public unsafe_istream {...};

stream_MT 提供了使每个 iostream 类成为 MT 安全的类所需的互斥锁,另外,还提供了动态启用和禁用这些锁的功能,以便可以动态更改 MT 安全属性。用于 I/O 转换和缓冲区管理的基本功能划入 unsafe_ 类,为库增加 MT 安全的功能则划入派生类。每个类的 MT 安全版本都包含与 unsafe_ base 类相同的受保护的公共成员函数。MT 安全版本类中的每个成员函数都可用作包装器,它可以锁定对象、调用 unsafe_ base 中的相同函数以及解锁对象。


注 –

streambuf是从非安全类派生而来的。streambuf 类的受保护的公共成员函数可以通过锁定来重入。此外还提供了带 _unlocked 后缀的已解锁版本。


10.4.1.1 公共转换例程

已向 iostream 接口添加了一组 MT 安全的重入公共函数。用户指定的缓冲区被作为每个函数的附加参数。这些函数如下所述:

表 10–2 多线程安全的可重入公共函数

功能 

说明  

char *oct_r (char *buf,

int buflen,

long num,

int width)

将指针返回到用八进制表示数字的 ASCII 字符串。非零宽度假定为格式化的字段宽度。返回值不保证指向用户提供缓冲区的开始部分。 

char *hex_r (char *buf,

int buflen,

long num,

int width)

将指针返回到用十六进制表示数字的 ASCII 字符串。非零宽度假定为格式化的字段宽度。返回值不保证指向用户提供缓冲区的开始部分。 

char *dec_r (char *buf,

int buflen,

long num,

int width)

将指针返回到用十进制表示数字的 ASCII 字符串。非零宽度假定为格式化的字段宽度。返回值不保证指向用户提供缓冲区的开始部分。 

char *chr_r (char *buf,

int buflen,

long num,

int width)

返回指向包含字符 chr 的 ASCII 字符串的指针。如果宽度非零,则字符串包含后跟 chrwidth 个空格。返回值不保证指向用户提供缓冲区的开始部分。

char *form_r (char *buf,

int buflen,

long num,

int width)

返回由 sprintf 格式化字符串的指针,其中使用了格式字符串 format 和其余参数。缓冲区必须具有足够的空间以包含格式化的字符串。


注 –

用来确保与早期版本的 libC 兼容的 iostream 库的公共转换例程(octhexdecchrform是 MT 安全的。


10.4.1.2 使用 MT 安全的 libC 库进行编译和链接

生成使用 libC 库的 iostream 类以在多线程环境中运行的应用程序时,应使用 -mt 选项。此选项可将 -D_REENTRANT 传递给预处理程序,并将 -lthread 传递给链接程序。


注 –

请使用 -mt(而不是 -lthread)与 libClibthread 链接。该选项确保了库的正确链接顺序。错误使用 -lthread 可能会导致应用程序无法正常运行。


对于使用 iostream 的单线程应用程序,不需要使用特殊的编译器和链接程序选项。缺省情况下,编译器会与 libC 库链接。

10.4.1.3 MT 安全的 iostream 限制

有关 iostream 库的 MT 安全性的限制定义意味着,用于 iostream 的许多编程常用方式在使用共享 iostream 对象的多线程环境中是不安全的。

检查错误状态

要实现 MT 安全,必须在具有可能导致出现错误的 I/O 操作的关键区中进行错误检查。以下示例说明了如何检查错误:


示例 10–1 检查错误状态


#include <iostream.h>
enum iostate {IOok, IOeof, IOfail};

iostate read_number(istream& istr, int& num)
{
    stream_locker sl(istr, stream_locker::lock_now);

    istr >> num;

    if (istr.eof()) return IOeof;
    if (istr.fail()) return IOfail;
    return IOok;
}

在此示例中,stream_locker 对象 sl 的构造函数锁定 istream 对象 istr。在 read_number 终止时调用的析构函数 sl 解锁 istr

获取通过上次未格式化输入操作提取的字符

要实现 MT 安全,必须在执行上次输入操作和 gcount 调用这一期间独占使用 istream 对象的线程内调用 gcount 函数。以下示例说明了对 gcount 的调用:


示例 10–2 调用 gcount


#include <iostream.h>
#include <rlocks.h>
void fetch_line(istream& istr, char* line, int& linecount)
{
    stream_locker sl(istr, stream_locker::lock_defer);

    sl.lock(); // lock the stream istr
    istr >> line;
    linecount = istr.gcount();
    sl.unlock(); // unlock istr
    ...
}

在此示例中,stream_locker 类的成员函数 lockunlock 定义了程序中的互斥区域。

用户定义的 I/O 操作

要实现 MT 安全,必须锁定为用户定义类型定义且涉及对各个操作进行特定排序的 I/O 操作,才能定义关键区。以下示例说明了用户定义的 I/O 操作:


示例 10–3 用户定义的 I/O 操作


#include <rlocks.h>
#include <iostream.h>
class mystream: public istream {

    // other definitions...
    int getRecord(char* name, int& id, float& gpa);
};

int mystream::getRecord(char* name, int& id, float& gpa)
{
    stream_locker sl(this, stream_locker::lock_now);

    *this >> name;
    *this >> id;
    *this >> gpa;

    return this->fail() == 0;
}

10.4.1.4 减少多线程安全类的性能开销

使用此版本的 libC 库中的 MT 安全类会导致一些性能开销,即使是单线程应用程序中也是如此,但如果使用 libCunsafe_ 类,则可避免此开销。

可以使用作用域解析运算符执行基类 unsafe_ 的成员函数,例如:


    cout.unsafe_ostream::put(’4’);

    cin.unsafe_istream::read(buf, len);

注 –

unsafe_ 类不能在多线程应用程序中安全地使用。


可以使 coutcin 对象成为不安全对象,然后执行正常操作,而不是使用 unsafe_ 类。这会稍微降低性能。以下示例说明了如何使用 unsafe coutcin


示例 10–4 禁用多线程安全


#include <iostream.h>
//disable mt-safety
cout.set_safe_flag(stream_MT::unsafe_object);    
//disable mt-safety
cin.set_safe_flag(stream_MT::unsafe_object);    
cout.put(”4’);
cin.read(buf, len);

iostream 对象是 MT 安全对象时,有互斥锁定保护对象的成员变量。该锁定给仅在单线程环境中执行的应用程序增加了不必要的开销。为了提高性能,可以动态地启用或禁用 iostream 对象的 MT 安全性。以下示例使 iostream 对象成为 MT 不安全的对象:


示例 10–5 切换到多线程不安全


fs.set_safe_flag(stream_MT::unsafe_object);// disable MT-safety
    .... do various i/o operations

可以在多个线程共享 iostream 的情况下(例如,在只有一个线程的程序中,或在每个 iostream 都是线程专用的程序中),在代码中安全地使用 MT 不安全的流。

如果显式在程序中插入同步,还可以在多个线程共享 iostream 的环境中安全地使用 MT 不安全的 iostream。以下示例说明了该技术:


示例 10–6 在多线程不安全的对象中使用同步


    generic_lock();
    fs.set_safe_flag(stream_MT::unsafe_object);
    ... do various i/o operations
    generic_unlock();

其中,函数 generic_lockgeneric_unlock 可以是使用诸如互斥锁、信号或读取器/写入器锁定等基元的任何同步机制。


注 –

libC 库提供的 stream_locker 类是实现这一目的的首选机制。


有关更多信息,请参见10.4.5 对象锁定