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 后缀的已解锁版本。
已向 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 字符串的指针。如果宽度非零,则字符串包含后跟 chr 的 width 个空格。返回值不保证指向用户提供缓冲区的开始部分。 |
char *form_r (char *buf, int buflen, long num, int width) |
返回由 sprintf 格式化字符串的指针,其中使用了格式字符串 format 和其余参数。缓冲区必须具有足够的空间以包含格式化的字符串。 |
用来确保与早期版本的 libC 兼容的 iostream 库的公共转换例程(oct、hex、dec、chr 和 form)不是 MT 安全的。
生成使用 libC 库的 iostream 类以在多线程环境中运行的应用程序时,应使用 -mt 选项。此选项可将 -D_REENTRANT 传递给预处理程序,并将 -lthread 传递给链接程序。
请使用 -mt(而不是 -lthread)与 libC 和 libthread 链接。该选项确保了库的正确链接顺序。错误使用 -lthread 可能会导致应用程序无法正常运行。
对于使用 iostream 类 的单线程应用程序,不需要使用特殊的编译器和链接程序选项。缺省情况下,编译器会与 libC 库链接。
有关 iostream 库的 MT 安全性的限制定义意味着,用于 iostream 的许多编程常用方式在使用共享 iostream 对象的多线程环境中是不安全的。
要实现 MT 安全,必须在具有可能导致出现错误的 I/O 操作的关键区中进行错误检查。以下示例说明了如何检查错误:
#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 的调用:
#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 类的成员函数 lock 和 unlock 定义了程序中的互斥区域。
要实现 MT 安全,必须锁定为用户定义类型定义且涉及对各个操作进行特定排序的 I/O 操作,才能定义关键区。以下示例说明了用户定义的 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; } |
使用此版本的 libC 库中的 MT 安全类会导致一些性能开销,即使是单线程应用程序中也是如此,但如果使用 libC 的 unsafe_ 类,则可避免此开销。
可以使用作用域解析运算符执行基类 unsafe_ 的成员函数,例如:
cout.unsafe_ostream::put(’4’); |
cin.unsafe_istream::read(buf, len); |
unsafe_ 类不能在多线程应用程序中安全地使用。
可以使 cout 和 cin 对象成为不安全对象,然后执行正常操作,而不是使用 unsafe_ 类。这会稍微降低性能。以下示例说明了如何使用 unsafe cout 和 cin:
#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 不安全的对象:
fs.set_safe_flag(stream_MT::unsafe_object);// disable MT-safety .... do various i/o operations |
可以在多个线程未共享 iostream 的情况下(例如,在只有一个线程的程序中,或在每个 iostream 都是线程专用的程序中),在代码中安全地使用 MT 不安全的流。
如果显式在程序中插入同步,还可以在多个线程共享 iostream 的环境中安全地使用 MT 不安全的 iostream。以下示例说明了该技术:
generic_lock(); fs.set_safe_flag(stream_MT::unsafe_object); ... do various i/o operations generic_unlock(); |
其中,函数 generic_lock 和 generic_unlock 可以是使用诸如互斥锁、信号或读取器/写入器锁定等基元的任何同步机制。
libC 库提供的 stream_locker 类是实现这一目的的首选机制。
有关更多信息,请参见10.4.5 对象锁定。