文档章节

Writing reentrant and threadsafe code

n
 nodouble
发布于 2015/03/01 20:38
字数 2804
阅读 16
收藏 0
点赞 0
评论 0

本文是一篇译文,原文连接:http://www-01.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.genprogc/writing_reentrant_thread_safe_code.htm?cp=ssw_aix_61%2F13-3-12-18


在单线程程序中只存在一条控制流,因此单线程程序代码无需是可重入与线程安全的。而在多线程的程序中,同一个函数和资源则可能会被若干控制流同时访问。

为了保护资源的完整性,多线程程序的代码必须是可重入且线程安全的。

可重入与线程安全都与函数处理资源的方式有关,并且它们是两个独立的概念:一个函数可以是可重入的、或者线程安全的、或者既是可重入又是线程安全的。

本节包含了关于编写可重入与线程安全程序的内容,但并不涵盖编写高效线程的主题。高效线程程序是指高效的并行化程序,必须在程序的设计阶段就考虑到线程的效率问题。现有的单线程程序也可以改造为高效线程程序,但这需要对代码进行完全的重新设计与编写。


Reentrance

一个可重入的函数不能在连续的调用中持有同一个静态数据(not hold static data over successive calls),也不能返回指向静态数据的指针。所有的数据都应该由函数调用者提供。可重入函数中不能够调用非可重入函数。

一个非可重入的函数常常(但不总是)可以通过它的外部接口和使用方法被识别出来。例如,“strtok”函数就是非可重入的,因为它持有了待分割的字符串。“ctime“函数也不是可重入的,它返回了一个指向静态数据的指针,而这个指针在每次调用时都会被覆盖。


Thread safety

一个线程安全的函数通过“锁“来保护对共享资源的并发访问。线程安全只与函数的实现有关,而不影响它的外部接口

在C语言中,本地变量是在栈上动态分配的。因此,任何不使用静态数据和其他共享资源的函数都是线程安全的,如下面的例子所示:

/* threadsafe function */
int diff(int x, int y)
{
        int delta;

        delta = y - x;
        if (delta < 0)
                delta = -delta;

        return delta;
}

另外,使用全局数据(global data)是非线程安全的。全局数据应该由每个线程来维护或封装,并因此能够被顺序访问。一个线程可以读到与其他线程的错误相关的错误码。在AIX中,每个线程都有自己的errno值。


Making a function reentrant

在大多数情况下,将非可重入函数替换为可重入函数需要改变接口,而改变接口就是为了实现可重入性。非可重入函数不能用在多线程程序中。更进一步来说,无法使一个非可重入函数变为线程安全的


Returning data

许多非可重入函数返回一个指向静态数据的指针,这可以通过下面的几种方式来避免:

  • 返回一个“动态分配的数据“。在这种情况下,由调用者负责进行存储空间的释放工作。这样做的好处是无需修改接口,但是无法保证向后兼容性。现有的单线程程序直接使用这种新函数而不做修改的话会引发内存泄漏的问题;

  • 使用“由调用者提供的存储区“。尽管需要对接口进行修改,但还是推荐使用这种方法;

例如,一个strtoupper函数,将一个字符串转换为大写,可以由下面的代码片段来实现:

/* non-reentrant function */
char *strtoupper(char *string)
{
        static char buffer[MAX_STRING_SIZE];
        int index;

        for (index = 0; string[index]; index++)
                buffer[index] = toupper(string[index]);
        buffer[index] = 0

        return buffer;
}

这个函数是不可重入的(也是非线程安全的),若采用返回动态分配数据的方式使它变为可重入的,代码看起来可能是下面这样的:

/* reentrant function (a poor solution) */
char *strtoupper(char *string)
{
        char *buffer;
        int index;

        /* error-checking should be performed! */
        buffer = malloc(MAX_STRING_SIZE);

        for (index = 0; string[index]; index++)
                buffer[index] = toupper(string[index]);
        buffer[index] = 0

        return buffer;
}

一个更好的解决方案是修改接口,由调用者来提供输入&输出字符串的存储区,代码片段如下:

/* reentrant function (a better solution) */
char *strtoupper_r(char *in_str, char *out_str)
{
        int index;

        for (index = 0; in_str[index]; index++)
        out_str[index] = toupper(in_str[index]);
        out_str[index] = 0

        return out_str;
}

C标准库就是采用了这种“由调用者提供存储区的方式“将非可重入的函数改写为可重入的。


Keeping data over successive calls

没有数据应该在相继的函数调用之间被保存,因为不同的线程可能会先后来调用这个函数。如果一个函数必须在相继的调用间保存一些数据,例如一个工作缓冲区或者指针,那么这些数据必须由调用者来提供。

考虑下面的例子。A函数返回一个字符串中连续的小写字符,字符串只在第一次调用的时候提供,就像strtok函数所做的那样,当到达字符串结尾时函数返回0。代码如下:

/* non-reentrant function */
char lowercase_c(char *string)
{
        static char *buffer;
        static int index;
        char c = 0;

        /* stores the string on first call */
        if (string != NULL) {
                buffer = string;
                index = 0;
        }

        /* searches a lowercase character */
        for (; c = buffer[index]; index++) {
                if (islower(c)) {
                        index++;
                        break;
                }
        }
        return c;
}

这个函数不是可重入的,为了使它变为可重入的,静态数据index变量必须由调用者来维护。可重入的版本实现如下:

/* reentrant function */
char reentrant_lowercase_c(char *string, int *p_index)
{
        char c = 0;

        /* no initialization - the caller should have done it */

        /* searches a lowercase character */
        for (; c = string[*p_index]; (*p_index)++) {
                if (islower(c)) {
                        (*p_index)++;
                        break;
                  }
        }
        return c;
}

函数的接口变了,所以它的用法也随之改变。调用者需要在每次调用时提供字符串,并且在第一次调用时将index初始化为0,代码片段如下:

char *my_string;
char my_char;
int my_index;
...
my_index = 0;
while (my_char = reentrant_lowercase_c(my_string, &my_index)) {
        ...
}


Making a function threadsafe

在多线程的程序中,所有会被多个线程调用的函数都应该是线程安全的。然而,也存在一种变通的方法在多线程程序中使用非线程安全的函数。非可重入的函数通常都不是线程安全的,但把它们改写为可重入函数后常常也会令它们成为线程安全的。


Locking shared resources

使用静态数据或其他任何共享资源(例如,文件和终端)的函数,为了做到线程安全,都需要通过“锁”机制来实现对这些资源的顺序化访问。例如,下面的函数不是线程安全的:

/* thread-unsafe function */
int increment_counter()
{
        static int counter = 0;

        counter++;
        return counter;
}

为了做到线程安全,静态变量counter应该通过一个“静态锁(static lock)“被保护起来,如下:

/* pseudo-code threadsafe function */
int increment_counter();
{
        static int counter = 0;
        static lock_type counter_lock = LOCK_INITIALIZER;

        pthread_mutex_lock(counter_lock);
        counter++;
        pthread_mutex_unlock(counter_lock);
        return counter;
}

在一个使用线程库的多线程应用程序中,应该使用互斥锁来实现资源的顺序访问。独立的库则可能需要在线程上下文之外工作,那么就使用其他类型的锁。


Workarounds for thread-unsafe functions

可以通过一种变通的方法在多线程程序中调用非线程安全的函数。这种方法是有用的,尤其是在多线程程序中使用非线程安全的库时(为了测试或正在等待线程安全的库版本时)。这些变通带来了一些额外的开销,因为它包括对全部函数甚至函数组进行序列化。下面是一些可能的变通方法:

  • 为这个库使用一把全局的锁,并在每次使用库的时候锁住它(调用库函数或使用库全局变量)。这个方案会带来性能瓶颈,因为给定时刻只有一个线程能够访问库的任一部分。下面的伪代码给出的方案只有在库极少被访问时才是可接受的,或者是作为一种初步的、快速的变通方案;

/* this is pseudo code! */

lock(library_lock);
library_call();
unlock(library_lock);

lock(library_lock);
x = library_var;
unlock(library_lock);
  • 为每个或每组库元素(函数或者全局变量)使用一把锁。这个方案实现起来在某种程度上要比前一个例子更加复杂,但是它能够改善性能。因为这种方案只会在应用程序代码而不是库中使用,可以使用互斥锁;

/* this is pseudo-code! */

lock(library_moduleA_lock);
library_moduleA_call();
unlock(library_moduleA_lock);

lock(library_moduleB_lock);
x = library_moduleB_var;
unlock(library_moduleB_lock);


Reentrant and threadsafe libraries

可重入和线程安全的库被广泛使用在并行(和异步)程序环境中而不仅仅是线程中。总是编写可重用和线程安全的函数是一种好的编码实践。


Using libraries

一些由AIX Base Operating System提供的库是线程安全的。在当前的AIX版本中,下列库是线程安全的:

  • Standard C library(libc.a)

  • Berkeley compatibility library(libbsd.a)

有些标准C子函数是非可重入的,例如 ctime 和 strtok函数。这些函数的可重入版本的名字是由在非可重入版本的函数名后加上_r后缀构成的。

在编写多线程程序时,使用可重入的函数版本来替代原始的版本。例如,下面的代码段:

token[0] = strtok(string, separators);
i = 0;
do {
        i++;
        token[i] = strtok(NULL, separators);
} while (token[i] != NULL);

多线程程序中应该被替换为如下形式:

char *pointer;
...
token[0] = strtok_r(string, separators, &pointer);
i = 0;
do {
        i++;
        token[i] = strtok_r(NULL, separators, &pointer);
} while (token[i] != NULL);

非线程安全的库只能在单线程程序中使用。要确保只有一个线程在使用库,否则,程序可能会出现不可预期的行为,甚至停止运行。


Converting libraries

当要将一个现有的库转换为可重入与线程安全的库时,考虑以下几个方面,这些内容只适用于C语言库

  • 识别导出的全局变量。这些变量通常定义在一个头文件中,并伴有export关键字。导出的全局变量应该被封装起来。这些变量应该被私有化(在库函数源代码中通过static关键字定义),并创建访问函数(读和写);

  • 识别静态变量和其他共享资源。静态变量通常通过static关键字定义。锁应该伴随所有的共享资源。锁的粒度决定了锁的数量,并进而影响到库的性能。可以使用一次性初始化方式来对锁进行初始化;

  • 识别非可重入函数并把它们可重入化。更多信息参见 Making a Function Reentrant

  • 识别非线程安全函数并把它们线程安全化。更多信息参见 Making a Function threadsafe




© 著作权归作者所有

共有 人打赏支持
n
粉丝 3
博文 34
码字总数 40111
作品 0
东城
每日科技英文30: 可再入性和线程的互斥

今日要点: 可再入性及线程互斥的定义 疑问代词(5个)+ever,表示:不论/无论....,都/就... Reentrant(可再入的) Said of software that can be executed multiple times simultaneously.(缺...

随风而行之青衫磊落险峰行 ⋅ 2017/11/28 ⋅ 0

Intrinsic Locks and Synchronization

Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.") Intrin......

恋空御月 ⋅ 2016/08/19 ⋅ 0

HPC Template Library, A Supplement to the STL

Download source Download HTLDocs052 Author: Anthony Daniels Introduction This package is intended to provide the user with threadsafe containers and classes for applications in ......

trident99 ⋅ 2017/12/15 ⋅ 0

分布式系统的跟踪系统Dubbo RPC处理

分布式系统的跟踪系统Dubbo RPC处理 接着前一篇博文http://blog.csdn.net/doctor_who2004/article/details/46974695 上篇只是提供了一个思想,今天具体给出dubbo rpc 处理细节。 dubbo prc处...

Beaver_ ⋅ 2015/10/12 ⋅ 1

Locks in Java(翻译blog)

simple-lock from Java 5 the package java.util.concurrent.locks contains several lock implementations, so you may not have to implement your own locks. But you will still need to......

o0无忧亦无怖 ⋅ 2015/10/08 ⋅ 0

优雅的让一个类在线程安全和线程非安全间切换

一个良好的多线程库,不应当一刀切的全加锁。因为有些时候,虽然是多线程环境,但可能依照设计一个类只会被一个线程操作,这个时候加锁是多余的,纯浪费性能,但另一些场景又需要它是线程安全...

技术小胖子 ⋅ 2017/11/08 ⋅ 0

Jafka源码粗略解读之一

相关资料 Jafka是sohu的adyliu开源的Kafka的完整Java实现(Kafka本身是用Scala的)。没有学习Scala的计划,又想研究研究MQ,那就不妨从Jafka入手了。 关于Jafka有个slides,地址:https://www...

黄亿华 ⋅ 2013/08/14 ⋅ 0

Jsoup 1.10.1 发布,Java 的 HTML 解析器

Jsoup 1.10.1 发布了,Jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操作方法来取出和操作数据。更...

淡漠悠然 ⋅ 2016/10/24 ⋅ 7

嵌入式NoSQL引擎--UnQLite

UnQLite 是一个嵌入式的软件库,实现了自容器、无服务端、零配置和支持事务的 NoSQL 数据库引擎。UnQLite 是一个文档存储数据库,类似于 MongoDB, Redis, CouchDB 等。同时也是一个标准的 Ke...

匿名 ⋅ 2013/05/23 ⋅ 5

Apache DirectMemory 0.2 发布,多层缓存系统

Apache DirectMemory 是一个多层的缓存系统,特性包括无堆的内存管理用于支持大规模的 Java 对象,而不会影响 JVM 垃圾收集器的性能。 Apache DirectMemory 0.2 发布了,改进记录: 改进 [D...

oschina ⋅ 2013/09/18 ⋅ 3

没有更多内容

加载失败,请刷新页面

加载更多

下一页

前台对中文编码,后台解码

前台:encodeURI(sbzt) 后台:String param = URLDecoder.decode(sbzt,"UTF-8");

west_coast ⋅ 51分钟前 ⋅ 0

VS2015配置并运行汇编(一步一步照图做)【vs2017的链接在最后】

前言 我是上学期学的汇编,因为有vs又不想用课上教的麻烦的dosbox以及masm32,但是一直没找到高亮插件和能调试的(难在运行不了而找不到答案上,出现的错误在最后放出,还请先达们不吝指点)...

simpower ⋅ 今天 ⋅ 0

一起读书《深入浅出nodejs》-node模块机制

node 模块机制 前言 说到node,就不免得提到JavaScript。JavaScript自诞生以来,经历了工具类库、组件库、前端框架、前端应用的变迁。通过无数开发人员的努力,JavaScript不断被类聚和抽象,...

小草先森 ⋅ 今天 ⋅ 0

Java桌球小游戏

其实算不上一个游戏,就是两张图片,不停的重画,改变ball图片的位置。一个左右直线碰撞的,一个有角度碰撞的。 左右直线碰撞 package com.bjsxt.test;import javax.swing.*;import j...

森林之下 ⋅ 今天 ⋅ 0

你真的明白RPC 吗?一起来探究 RPC 的实质

你真的明白RPC 吗?一起来探究 RPC 的实质 不论你是科班出身还是半路转行,这么优秀的你一定上过小学语文,那么对扩句和缩句你一定不陌生。缩句就是去除各种修饰提炼出一句话的核心,而不失基...

AI9o後 ⋅ 今天 ⋅ 0

z-index设置失效?

今天碰到了一个问题,就是在给li设置提示框的时候,有用到遮罩效果,本来想把对应的出现在最顶层,可是不管将li设置的z-index值设为多大,li都没有出现在遮罩层之上。 我在网上查了z-index设...

IrisHunag ⋅ 今天 ⋅ 0

CyclicBarrier、CountDownLatch以及Semaphore使用及其原理分析

CyclicBarrier、CountDownLatch以及Semaphore是Java并发包中几个常用的并发组件,这几个组件特点是功能相识很容易混淆。首先我们分别介绍这几个组件的功能然后再通过实例分析和源码分析其中设...

申文波 ⋅ 今天 ⋅ 0

Java对象的序列化与反序列化

Java对象的序列化与反序列化

Cobbage ⋅ 今天 ⋅ 0

Sqoop

1.Sqoop: 《=》 SQL to Hadoop 背景 1)场景:数据在RDBMS中,我们如何使用Hive或者Hadoop来进行数据分析呢? 1) RDBMS ==> Hadoop(广义) 2) Hadoop ==> RDBMS 2)原来可以通过MapReduce I...

GordonNemo ⋅ 今天 ⋅ 0

全量构建和增量构建的区别

1.全量构建每次更新时都需要更新整个数据集,增量构建只对需要更新的时间范围进行更新,所以计算量会较小。 2.全量构建查询时不需要合并不同Segment,增量构建查询时需要合并不同Segment的结...

无精疯 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部