说明如何使用 Informix C DataBlade API 内存管理函数。

简介

UDR 不能调用标准操作系统内存分配函数,如 malloc()calloc()realloc()free() 。 关于体系结构问题的详细说明,请参阅 对用于 DataBlade 模块的限制进行编码 技术说明。

DataBlade API 提供两个让 UDR 从 IDS 共享内存动态分配内存的函数集:

  1. 用户内存函数:

    • mi_alloc()
    • mi_zalloc()
    • mi_dalloc()
    • mi_free()

    这些函数比已命名的内存函数更常用,并适合于在仅有一条 SQL 语句的上下文中执行的 UDR。 在查询完成后,服务器自动释放由 UDR 分配的内存。

    下面的“ 用户函数(mi_alloc) ”一节提供了关于如何使用它们的示例。

  2. 已命名的内存函数:
    • mi_named_alloc()
    • mi_named_zalloc()
    • mi_named_get()
    • mi_named_free()

    已命名的内存函数所分配的内存,可由多个 UDR 访问,这些 UDR 在(可能)有多条 SQL 语句的上下文中执行。 当查询完成时,内存不被释放,而是仍然保持可用。(下面的“ 如何以及何时释放内存 ”一节描述了如何释放它。) 已命名的内存可能要求您通过多线程来管理并行访问。

    下面的“ 已命名的内存函数(mi_named_alloc) ”一节显示了如何使用它们,“ 管理并发性 ”一节说明了如何通过多线程管理并行访问。

本技术说明的目标是阐明何时使用用户函数以及何时使用已命名的内存函数。

因为 DataBlade API 文档只涵盖公用函数,所以本技术说明的先前版本将内存函数的类别划分为公用和半公用函数。 从 IBM Informix DataBlade API Programmer's Manual,版本 9.3 开始,这两个函数集分别被记录和标识为“用户内存”函数和“已命名的内存”函数。 本技术说明现在采用该约定。 (可以从 IBM Informix 在线文档网站 下载手册。)

如果不用 mi_* 函数会发生什么呢?

malloc() 和其它 OS 内存函数引起的问题包括:

  • 在执行 UDR 的虚拟处理器(VP)堆上分配内存。

    • 如果线程切换到另一个 VP,则该内存的指针将失效。

    • 因为其它 VP 不知道该内存是用 malloc() 分配的,所以它们可能在扩展虚拟内存池时使用同一地址空间,导致重叠的内存分配。

  • 内存没有被释放,导致内存泄漏。 例如,UDR 的返回值如果大于 4 字节就必须动态地分配。

    • 如果您使用 mi_* 函数,那么,当不再需要返回值时服务器会自动释放它。 还有,如果在 UDR 执行期间出现任何异常,则服务器释放内存。

    • 如果您使用 malloc() ,则在服务器使用完该值后不会释放它。而且发生异常时也不会释放它。

请参阅下面的“ 关于这个主题的更多信息 ”一节,以获得详细描述该问题的 IDN 白皮书列表。


回页首

内存是如何在内部存储的

DataBlade API mi_* 内存分配函数从 IDS 共享内存分配内存。

内存持续时间

每个 mi_* 内存分配都有与之相关的持续时间。下面是关于持续时间的简明摘要。 有关更多详细信息,请参阅 IBM Informix DataBlade API Programmer's Manual,版本 9.3 中的“Managing Memory”的章节,可以从 IBM Informix 在线文档网站 下载该手册。

持续时间 描述
PER_ROUTINE 一次 UDR 调用
PER_COMMAND 子查询
PER_STMT_EXEC' (9.3 以前的版本)SQL 语句( SELECTINSERTUPDATE 等)。
PER_STMT_EXEC (9.3)当前 SQL 语句。
PER_STMT_PREP (9.3)预编译的 SQL 语句。
PER_TRANSACTION 当前客户机事务( BEGIN WORKCOMMIT/ABORT WORK
PER_SESSION 当前客户机会话
PER_SYSTEM IDS 系统全局,在服务器关机之前一直保持有效

两个新的 9.3 持续时间 PER_STMT_EXECPER_STMT_PREP 取代了现已弃用的 PER_STATEMENT 内存持续时间。

内存池

服务器在内部通过持续时间分配内存池。

您可以用 onstat -g mem 命令监控为特定客户机会话分配了多少 DataBlade API 共享内存。

9.3 服务器的输出包括类似如下所示的行:

CMD.53.72    V     ...
EXE.53.72    V     ...
SES.53.72    V     ...
PRP.53.72    V     ...
RTN.53.72    V     ...

前三个字符标识内存池。下面总结了持续时间代码:

RTN PER_ROUTINE
CMD PER_COMMAND
STM PER_STATEMENT(9.3 以前的版本)
EXE PER_STMT_EXEC(9.3)
PRP PER_STMT_PREP(9.3)
TRX PER_TRANSACTION
SES PER_SESSION
SYS PER_SYSTEM

以上输出中的“53”是运行查询的用户的会话标识,您可以用“ onstat -g ses 53 ”找出更多关于该会话的信息。“72”是 sqlexec 线程的线程标识。

您可能注意到 9.3 onstat 输出与先前发行版的输出不同。9.3 之前版本的服务器的输出包括如下所示的行:

23.RTN.SAPI V ....
23.STM.SAPI V ....

每行都以运行查询的用户的会话标识开始(“23”),后跟持续时间代码、然后是“ SAPI ”(意思是“服务器 API (Server API)”),SAPI 由 DataBlade API mi_* 函数为其分配内存。


回页首

如何以及何时释放内存

当其持续时间到期时,服务器自动释放内存。或者您可以使用适当的 mi_* 函数提前释放内存。

例如,您可以用 mi_alloc() 为 UDR 中的临时变量分配内存,然后用 mi_free() 在 UDR 返回前释放该内存。但不要释放您分配给 UDR 返回结果的内存! 当 SQL 语句中不再需要返回值时,服务器将释放该内存。

注意! 访问持续时间已过期的内存通常导致断言失败。下面关于“ 调试问题 ”一节说明了调试断言失败的方法。

好的,如果您希望提前进行自己的垃圾收集,那么您需要更多上下文来理解如何以及何时释放内存,以及应该使用什么函数。下一节描述了如何分配内存以及如何指定持续时间。“ 如何(以及何时)释放内存 ”一节特别详细地讲述了如果您打算在内存持续时间到期之前释放内存,该如何(以及 何时! )做到这一点。


回页首

用户函数(mi_alloc)

下面列出了用户内存函数:

mi_alloc() 以当前持续时间分配一块内存。缺省值是 PER_ROUTINE。
mi_zalloc() mi_alloc() 相同,但内存已置零 — 它的行为类似于 calloc()
mi_dalloc() 以指定的持续时间分配一块内存。 对于 UDR,您最常用的指定值是 PER_ROUTINE 或 PER_COMMAND。对于 VTI 和 VII 访问方法,开发人员可能指定 PER_STMT_EXEC(请参阅下面一节)。
mi_free() 释放由 mi_alloc()mi_zalloc()mi_dalloc() 分配的内存。

mi_alloc()mi_zalloc()mi_dalloc() 返回指向已分配内存的指针。然后,您在该指针上执行所有操作。 关于此类内存的关键点包括:

  • 总是通过由构造器函数返回的地址来访问内存。
  • 只有分配内存的 UDR 才能访问它。
  • 内存用于一个 SQL 查询。
  • 因为大多数 UDR 在仅有一个 SQL 查询的上下文中执行,所以这是最常见的已分配内存。

公用函数最常用的持续时间是 PER_ROUTINEPER_COMMAND 。 在查询完成后,服务器自动释放任何由 UDR 分配且您还未释放的内存。

有必要顺便提一下另一个分配内存的用户函数: mi_new_var() 分配 mi_lvarchar 结构。 有关详细信息,请参阅 用 mi_lvarchar 管理变长数据 技术说明。

示例:分配 PER_ROUTINE 内存

以下 MyLog10() UDR 的代码显示了如何使用 mi_alloc() 来为双精度返回结果分配 PER_ROUTINE 内存。

mi_double_precision *
MyLog10(mi_double_precision *x, MI_FPARAM *fp)
{
mi_double_precision *retval=NULL;

/* Initialize return value to NULL */
mi_fp_setreturnisnull(fp, 0, MI_TRUE);

/* Allocate return value */
retval = (mi_double_precision *)
mi_alloc(sizeof(mi_double_precision));
if(retval == (mi_double_precision *) NULL)
{

/* If mi_alloc failed, raise an exception */
mi_db_error_raise(NULL,
MI_EXCEPTION, "mi_alloc failed!");

/* Probably not reached; for information on
** exception handling, see chapter 10 of the
**
Informix DataBlade API Programmer's Manual
.
*/

return (mi_double_precision *)NULL;
}

/* Assign the return value by calling
** the math lib log10() function.
*/
*retval = log10(*x);

/* Return value is not NULL */
mi_fp_setreturnisnull(fp, 0, MI_FALSE);
return (retval);
}

缺省情况下, mi_alloc()PER_ROUTINE 持续时间分配内存。(可以通过调用 mi_switch_mem_duration() 来更改这个缺省值;请参阅 DataBlade API Programmer's Manual 。)

每个 UDR 调用都分配返回结果,在 UDR 完成时服务器自动释放该结果。 永远不应指望 PER_ROUTINE 内存在 UDR 返回之后仍然有效。(实际上,它在 下一个 UDR 调用之前被释放。)

UDR 可能在一个查询中被多次调用,每处理一行就调用一次。 例如,在下面的查询中, MyLog10 被调用了三次,每次用于表中的一行:

> select value,
>        MyLog10(value) MyLog10
> from   log_data;
value        mylog10
1.000000000000           0.00
2.000000000000 0.301029995664
3.000000000000 0.477121254719
3 row(s) retrieved.

UDR invocation #1
UDR invocation #2
UDR invocation #3

该 UDR 被调用了三次, PER_ROUTINE 三次分配了返回值,并且服务器每次都自动释放内存。

如果您觉得为每一行都分配返回值代价过高,尤其是对于为大型表执行的 UDR, 那么您是对的! 下一节研究了如何在第一次调用 UDR 时就一次性分配返回值,这样内存将对于所有返回值都保持有效,以便所有 UDR 调用能够使用它。

示例:分配 PER_COMMAND 内存

本节演示了如何修改 MyLog10() UDR 以分配 PER_COMMAND 内存,它在查询中 UDR 的所有调用期间保持有效。为了做到这一点:

  • 我们需要标识第一次 UDR 调用,此时我们将分配 PER_COMMAND 内存。

  • 我们需要一个地方来存储指向 PER_COMMAND 已分配内存的指针,以便后面的 UDR 调用可以取回该指针。

MI_FPARAM 结构提供了一个地方来用 mi_fp_setfuncstate() 存储指向 PER_COMMAND 内存的指针。 mi_fp_funcstate() 取回该指针,并在第一个 UDR 调用时返回 NULL。

mi_double_precision *
MyLog10(mi_double_precision *x, MI_FPARAM *fParam)
{
mi_double_precision *retval=NULL;

/* Initialize */
mi_fp_setreturnisnull(fParam, 0, MI_TRUE);

/* Fetch the pointer from the MI_FPARAM.
*/
retval = (mi_double_precision *)
mi_fp_funcstate(fParam);

/* mi_fp_funcstate() returns NULL on
** UDR Invocation #1.  So we know it is
** time to allocate the return result with
** a PER_COMMAND duration using mi_dalloc().
*/
if(retval==(mi_double_precision *) NULL)
{
retval=(mi_double_precision *)
mi_dalloc(sizeof(mi_double_precision),
PER_COMMAND);
if(retval==(mi_double_precision *) NULL)
{
mi_db_error_raise(NULL,
MI_EXCEPTION, "mi_alloc failed!");

/* Not reached, but
good coding style,
** especially for large projects that
** may forget what callbacks have been
** registered.
*/
return (mi_double_precision *)NULL;
}

/* Store the pointer in
the MI_FPARAM */
mi_fp_setfuncstate(fParam, (void *) retval);
}

/* Implied else: this is not the first time the
** UDR was invoked. We retrieved the pointer
** from the MI_FPARAM, and can simply use it.
*/
*retval=log10(*x);

/* Return value is not NULL */
mi_fp_setreturnisnull(fParam, 0, MI_FALSE);
return (retval);
}

VTI 和 VII 访问方法的说明

VTI / VII API 提供了特殊的描述符(类似于 MI_FPARAM )来存储指向用户数据的指针。 这些描述符被传递到许多访问方法任务:

  • MI_AM_SCAN_DESC 描述符被传递到 am_beginscanam_endscan 任务之间的访问方法例程。 可以用 mi_dalloc() 分配 PER_COMMAND 内存,并用 mi_scan_setuserdata() 存储指针。

  • MI_AM_TABLE_DESC 描述符被传递到 am_openam_close 任务之间的访问方法例程。 可以用 mi_dalloc() 分配 PER_STMT_EXEC 内存并用 mi_tab_setuserdata()MI_AM_TABLE_DESC 结构中存储指向该内存的指针。

如何(以及何时)释放内存

释放内存是可选的,因为当内存持续时间到期时,服务器会自动释放它。 虽然如此,但当查询正在运行时,释放不再需要的内存以减少 UDR 内存消耗是通常的做法。

以下是一些准则:

  • 绝不要释放分配给 UDR 返回值的内存。 服务器在不再需要该内存时会释放它。

  • 不要尝试在内存持续时间到期后释放它。

  • 不要用 mi_free() 释放任何数据结构(如 mi_lvarchar )。 那样做只会释放主 mi_lvarchar 结构本身而不是它指向的数据容器。DataBlade 在持续时间到期和释放内存以前很可能会发生内存泄漏。

  • 以合适的持续时间分配内存,尤其是如果您打算为以后的重用而在诸如 MI_FPARAM 之类的描述符中存储指向该内存的指针时。

    例如, MI_FPARAM 的持续时间是 PER_COMMAND

    • 如果分配内存时使用了太大的持续时间,则 DataBlade 很可能发生内存泄漏。因此,如果您用 mi_dalloc() 分配 PER_SESSION 内存并在 MI_FPARAM 中存储指针,那么,服务器在查询完成后会释放 MI_FPARAM ,而 PER_SESSION 内存将保持到客户机会话结束为止。

    • 如果分配内存时使用了太小的持续时间,则可能看到断言失败。 因此,如果您分配了 PER_ROUTINE 内存并在 MI_FPARAM 中存储其指针,则内存会在一次 UDR 调用之后被释放而指针对于后续的调用不再有效。

  • 除非分配了内存,否则就不要释放它。许多分配内存的 mi_* 函数拥有相应的释放函数,因此,显然第一个函数分配内存,也显然有某个函数可以用来释放它。 mi_row_create()mi_row_free() 就是一对很好的示例。

有时 DataBlade API 函数分配内存、该内存的持续时间是多少以及可以用什么函数来提前释放它这些都不明显。 IBM Informix DataBlade API Programmer's Manual,版本 9.3 中的图 13-5( PER_ROUTINE )、13-6( PER_COMMAND )、13-9( PER_STMT_EXEC )和 13-11( PER_SESSION )总结了由 DataBlade API 函数分配的内存的持续时间。

调试问题

onmode 调试标志

您可以启用内存梳理和内存池检查来调试断言失败。 本节描述了如何启用这些检查,但它们应该仅用于开发和调试。当启用时,它们将减慢生产环境下的应用程序的速度。

onmode -f 0x200 启用内存梳理。 接着服务器将:

  • 已分配但未初始化的内存标记为模式 0xfe
  • 已释放的内存标记为模式 0xfd
    • 9.1:仅梳理显式地释放的内存,如通过调用 mi_free()mi_var_free() 释放的内存。
    • 9.2:自动释放的内存也被梳理,如当分配的内存持续时间到期时被作为垃圾收集的内存。

启用内存池检查以捕获对已释放内存或界外位置的写操作:

  • 9.1: onmode -f 0x1
  • 9.2: onmode -f 0x40000

标志可以组合。例如, onmode -f 0x201 在 9.1 服务器中既启用内存梳理又启用内存池检查。 最终结果是断言失败很可能在更接近问题的地方发生。

您也可以使用 onconfig 参数来启用这些选项。例如,下列参数启用梳理:

CCFLAGS         0x200             # scribble

应该记住在您注释掉 CCFLAGS 参数并重新启动服务器之前,调试选项一直都有效。

新的 9.3 DataBlade API 函数

9.3 服务器引入了两个新的 mi_* 函数,可以用它们来生成有辅助作用的跟踪消息:

  • mi_get_duration_size() 让您确定内存池的大小。
  • mi_get_memptr_duration() 让您确定用某个 mi_* 函数分配的内存的持续时间。

回页首

已命名的内存函数(mi_named_alloc)

下面列出了已命名的内存函数:

mi_named_alloc() 以指定的持续时间分配一个已命名的内存块。
mi_named_zalloc() mi_named_alloc() 相同,但内存已置零。
mi_named_get() 通过提供名称和持续时间获得指向该内存的指针。
mi_named_free() 释放用 mi_named_alloc()mi_named_zalloc() 分配的内存。

已命名的内存将已分配的内存与名称和内存持续时间联系起来。 当您分配内存时,服务器在内部存储名称及其相应地址。 您总是可以通过提供名称和持续时间获得指向该内存的指针。

关于已命名内存的关键点包括:

  • 始终可以通过内存的名称和持续时间来获取指向它的指针。

  • 在分配内存的持续时间上下文中内存是全局的,因此可以由多个 UDR 访问,这个 UDR 在(可能)有多个查询上下文中执行,甚至可以由多个客户机会话访问它。

  • 在内存持续时间到期以前,它一直是可访问的。

  • 当您需要跨 UDR 高速缓存数据或在 UDR 之间共享内存时,应该使用已命名的内存。 样本用法包括:

    • 可以在 UDR 或会话之间共享的半静态查询信息。
    • 用于对 mi_routine_exec() 反复调用的会话级高速缓存函数(MI_FUNC_DESC)描述符。请参阅 Fastpath:从 C UDR 执行 SQL 例程
    • 索引方法,需要存储用于跨分段索引的索引扫描的“全局”信息。

  • 已命名的内存例程不锁定相关内存。

    mi_named_alloc()mi_named_zalloc() 分配被请求的内存块,并在跟踪已命名的内存的内部散列表中插入一个用于该已命名的内存的项。作为该散列表项的一部分,它还创建一个互斥锁,稍后可以用该互斥锁使用 mi_lock_memory()mi_try_lock_memory() 锁定已命名的内存。

    类似地, mi_named_get() 只返回指向已命名的内存的指针;它不锁定该已命名的内存。

    由 DataBlade 管理并发访问。

下一节提供了必须管理的并发性上下文问题中的已命名的内存代码示例。


回页首

管理并发性

正如前面所提到的,由 DataBlade 管理并发访问。但您必须知道它何时成为问题,以及如何管理它。

并发性问题

通常,内存持续时间越长,并发性就越有可能成为问题。 因此,第一步是理解并发性在哪里以及何时成为问题。

持续时间 = PER_ROUTINE 或 PER_COMMAND

让我们假设 UDR foo() 用一个公用函数来分配 PER_ROUTINE 内存或在 MI_FPARAM 中高速缓存 PER_COMMAND 内存。

在下面的查询中,有三个 foo() 的实例,并且每个 UDR 的实例将多次调用 foo()

select foo(column1)
from   my_table
where  foo(column1) = 1
or  foo(column2) = 2;

查询中的每个 foo() 实例都在其自己的上下文中执行,获得自己的 MI_FPARAM ,并分配仅对它自己可见的内存。这里不用管理并发性问题,由服务器管理并发性。

如果三个 foo() 实例都要共享数据,它们就需要分配一个持续时间至少为 PER_STMT_EXEC 的已命名的内存。

持续时间 > PER_COMMAND

让我们假定函数 foo() 也在内部管理名为 foobar 的已命名的内存。 每个 foo() 实例都设法得到已命名的内存块;如果该内存块还不存在,则该实例继续运行并分配它。给定下列查询:

select foo(column1)
from   my_table
where  foo(column1) = 1
or  foo(column2) = 2;

foobar 的内存持续时间确定了谁可以同步访问它:

  • PER_STMT_EXEC
    如果 foo() 分配给 foobar 的持续时间为 PER_STMT_EXEC,那么每个在同一 SQL 语句中执行的 foo() 将访问同一个 foobar 。 在 SQL 语句完成之后释放该内存。

  • PER_SESSION
    如果 foo() 分配给 foobar 的持续时间为 PER_SESSION,那么该客户机会话中的任何 SQL 查询都可以访问该内存。当数据库连接关闭时释放该内存。

  • PER_SYSTEM
    如果 foo() 分配给 foobar 的持续时间为 PER_SYSTEM ,那么,任何执行 foo() 的代码都访问同一个 foobar 。 该内存对于整个 IDS 服务器来说是全局的,并且在服务器关闭之前,它不会被释放。

基本准则

正如前面的“ 已命名的内存函数(mi_name_alloc) ”一节所提到的, mi_named_alloc()mi_named_zalloc()mi_named_get() 只返回指向内存的指针。已命名的内存例程都不锁定相关内存。

因此,即使对于仅读取已命名的内存而不更新它的线程,最安全的方法是锁定该内存,以防它已经处于被另一个线程更新的过程中。在 DataBlade 代码一致地管理内存的前提下,一旦线程拥有了对已命名的内存的锁,就可以保证一致的读取。

但请尽可能缩短持有锁的时间。应该在严格管理的、快速代码段中使用锁定接口,这些代码段让您在修改期间保护临界区。代码应该:

  1. mi_lock_memory()mi_try_lock_memory() 锁定内存
  2. 执行修改或一致的读。
  3. 一完成第 2 步就立即用 mi_unlock_memory() 解锁内存。

这种锁定接口不适于长期持有锁。 如果您需要长时间锁定数据,应该在客户机应用程序中用 SQL 来这么做。

注意! 别耽误时间!获取和释放锁都要快。 一旦您获取了锁,则 必须 释放它。

本节提供了使用 mi_lock_* 函数的样本代码。

示例:mi_lock_memory()

假定已声明了 MyInfo 结构:

typedef struct
{
mi_integer inited;
/* initialization flag */

...some other members...
}MyInfo;

并假定该代码提供了初始化该结构的 C 函数:

mi_integer MyInfo_init(MyInfo *my_info);

下面的样本代码分配已命名的内存,或者,如果已经分配了已命名的内存则获取它,然后管理针对该内存的并发读取和更新。

#include <mi.h>
#include <minmdur.h>
MyInfo *
GetMyInfo()
{
mi_string   *memname="MyInfo_memory",
msgbuf[80];
mi_integer  status;
MyInfo      *my_info = NULL;

/* Allocate the named memory. If it already
* exists, get it.
*/
status = mi_named_zalloc (sizeof(MyInfo),
memname, PER_SESSION, (void**) &my_info);
if(status == MI_NAME_ALREADY_EXISTS)
status = mi_named_get
(memname, PER_SESSION, (void **) &my_info);
switch(status)
{
case MI_ERROR:
mi_db_error_raise (NULL, MI_EXCEPTION,
"GetMyInfo: mi_named_get or mi_named_zalloc failed.");
return (MyInfo *)NULL;
break;

/* We got it. */
case MI_OK:
break;
case MI_NO_SUCH_NAME:
mi_db_error_raise (NULL, MI_EXCEPTION,
"GetMyInfo: no name after good get");
return (MyInfo *)NULL;
break;
default:
sprintf(msgbuf,
"GetMyInfo: mi_named memory case %d.", status);
mi_db_error_raise (NULL, MI_EXCEPTION, msgbuf);
return (MyInfo *)NULL;
break;
}

/*
* BEGIN CRITICAL SECTION.
*
* All access to the my_info structure is done
* inside this lock-protected section of code.
*
* If two threads try to initialize information
* at the same time, the second one blocks on
* the mi_lock_memory call.
*
* A reader also blocks so that it gets a
* consistent read if another thread is updating
* that memory.
*/
status = mi_lock_memory (memname, PER_SESSION);
switch(status)
{
case MI_ERROR:
mi_db_error_raise (NULL, MI_EXCEPTION,
"GetMyInfo: mi_lock_memory call failed.");
return (MyInfo *)NULL;
break;
case MI_OK:
break;
case MI_NO_SUCH_NAME:
mi_db_error_raise (NULL, MI_EXCEPTION,
"mi_lock_memory got MI_NO_SUCH_NAME.");
return (MyInfo *)NULL;
break;
default:
sprintf(msgbuf,
"GetMyInfo: mi_lock_memory case %d.",
status);
mi_db_error_raise (NULL, MI_EXCEPTION,
msgbuf);
return (MyInfo *)NULL;
break;
}

/* We have the lock. */ 

/* The mi_named_zalloc() call above zero'd out
* the structure, like calloc(). So if the inited
* flag is set to zero, we know that named memory
* has not been initialized yet.
*/
if (my_info->inited == 0)
{

/* In this block we populate the named
* memory structure. After initialization
* succeeds, we set the inited flag.
*
* If any operation fails, we MUST
* release the lock before calling
* mi_db_error_raise():
*
*  if (whatever != MI_OK)
*  {
*     mi_unlock_memory(memname, PER_SESSION);
*     mi_db_error_raise(NULL, MI_EXCEPTION,
*           "operation X failed!");
*     return (MyInfo *)NULL;
*  }
*
*/

my_info->inited = 1;
}
/* endif: MyInfo structure not initialized */
else
{

/* Update or get a consistent read here.
* Again, if we want to raise an exception,
* we must release the lock before calling
* mi_db_error_raise().
*/
}

/*
* END CRITICAL SECTION.
*/
mi_unlock_memory (memname, PER_SESSION);
return my_info;
}

示例:mi_try_lock_memory()

以下是另一种使用 mi_try_lock_memory() 的方法,如果另一个线程拥有锁则它立即返回。如果 10 次尝试之后还不能获取锁,这个代码将返回错误。

#include "mi.h"
#include "minmdur.h"
mi_integer
named_mem_example()
{
mi_string  * mymem;
mi_string  * mymem_name = "my_named_memory";
mi_integer memstat, lockstat, i;
memstat = mi_named_alloc(80, mymem_name,
PER_STMT_EXEC, &mymem);
if (memstat == MI_NAME_ALREADY_EXISTS)
memstat = mi_named_get(mymem_name,
PER_STMT_EXEC, &mymem);
if (memstat != MI_OK)
{
mi_db_error_raise(NULL, MI_EXCEPTION,
"oops named get or named alloc failed");
return MI_ERROR;
}

/* 10 tries is very pessimistic
*/
for (lockstat=MI_LOCK_IS_BUSY, i=0;
lockstat == MI_LOCK_IS_BUSY && i < 10;
i++)
{
lockstat=mi_try_lock_memory(mymem_name,
PER_STMT_EXEC);
switch(lockstat)
{
case MI_OK:
break;
case MI_LOCK_IS_BUSY:
mi_yield(); /* Yield the processor. */
break;
case MI_NO_SUCH_NAME:
mi_db_error_raise(NULL, MI_EXCEPTION,
"try lock said no name
after good get");
return MI_ERROR;
break;
case MI_ERROR:
mi_db_error_raise(NULL, MI_EXCEPTION,
"oops try lock error");
return MI_ERROR;
break;
default:
mi_db_error_raise(NULL, MI_EXCEPTION,
"oops unhandled try lock
status");
return MI_ERROR;
break;
}
}
/* Check the status after coming out of
the loop. */
if(lockstat == MI_LOCK_IS_BUSY)
{
mi_db_error_raise(NULL, MI_EXCEPTION,
"oops couldn't get lock");
return MI_ERROR;
}

/* We have the pointer, we
have the lock.
* Now we can update it or get a consistent
* read. In this case, we'll update it.
*/
sprintf (mymem, "We have the ptr. We have
the lock!");
mi_unlock_memory(mymem_name, PER_STMT_EXEC);
mi_return MI_OK;

}

释放锁定

服务器不会释放您可能已经获得的任何锁。在下列情况下,您 必须 释放所获得的任何锁:

  1. 当您完成对已命名的内存的读或写操作时。
  2. 在您用 mi_db_error_raise() 产生异常之前。
  3. 如果您调用的另一个 mi_* 函数在内部产生异常。

上面的示例演示了如何管理前两种情况。 第三种情况可以通过注册异常处理程序来管理, Informix DataBlade API Programmer's Manual 第 10 章对此进行了详细描述。

例如,让我们假定 上面的 GetMyInfo() 示例从查询表执行 SQL select 以将数据填入已命名的内存。 mi_exec() 是在内部产生异常的函数示例。我们应该注册一个异常回调,如果发生异常它就释放锁。 完成该任务的代码如下。

#include <mi.h>
#include <minmdur.h>
MyInfo *
GetMyInfo()
{
MyInfo      *my_info = NULL;
mi_string   *memname="MyInfo_memory",

/* ...
** Much code
above
removed
for brevity here.
** ...
*/

status = mi_named_zalloc (sizeof(MyInfo),
memname, PER_SESSION, (void**) &my_info);

...
status = mi_lock_memory (memname, PER_SESSION);

...
if (my_info->inited == 0)
{
MI_CONNECTION *conn=NULL;
MI_CALLBACK_HANDLE *cback=NULL;

/* In this block we populate the named
* memory structure. ...
*/

/* Get a connection handle. */

conn = mi_open(NULL,NULL,NULL);
if (conn == (MI_CONNECTION *)NULL)
{

/* Unlock it if mi_open failed!
*/
mi_unlock_memory(memname, PER_SESSION);
mi_db_error_raise(NULL, MI_EXCEPTION,
"mi_open failed!");
return (MyInfo *)NULL;
}

/* Register a callback for that conn handle.
*/
cback = mi_register_callback(conn, MI_Exception,
error_callback, NULL, NULL);

/* Execute query. error_callback() does not
* return control to this UDR, so this code
* does not check the mi_exec return status.
*/
mi_exec(conn,
"select * from MyInfo_lookup;"
MI_QUERY_BINARY);

/* Handle query results and
* populate named memory.
*/

...
mi_close(conn);
my_info->inited = 1;
}
/* endif: MyInfo structure not initialized
*/
else
{

/* Do update or consistent read here. */
}
mi_unlock_memory (memname, PER_SESSION);

/*
* END CRITICAL SECTION.
*/
return my_info;
}
/* error_callback() releases the named memory lock.
* It also outputs trace messages for the __myErrors__
* trace class.
*/
MI_CALLBACK_STATUS
error_callback (MI_EVENT_TYPE type, MI_CONNECTION *conn,
void *ius_info, void *user_data)
{
mi_integer      state_type, status, sqlcode;
mi_string       *s,
sqlstate[6],
/* 5 character code
*/
mesg[256],
*r="error_callback",
*memname="MyInfo_memory",
*trace_class="__myErrors__";
MI_ERROR_DESC   *p=NULL;

/* Release the lock. */
status = mi_unlock_memory (memname, PER_SESSION);
switch(status)
{
case MI_OK:
DPRINTF(trace_class, 1,
("%s: mi_unlock_memory status = MI_OK", r));
break;
case MI_NO_SUCH_NAME:

/* Hmmmm. Remember that named memory is
* allocated, obtained, and locked based on
* name and duration.
*/
DPRINTF(trace_class, 1,
("%s: mi_unlock_memory status = MI_NO_SUCH_NAME",
r));
break;
case MI_ERROR:
DPRINTF(trace_class, 1,
("%s: mi_unlock_memory status = MI_ERROR", r));
break;
default:
DPRINTF(trace_class, 1,
("%s: mi_unlock_memory status = %d",
r, status));
break;
}
if(type != MI_Exception)
{
DPRINTF(trace_class, 1,
("error_callback called with wrong event type %d.",
type));
return MI_CB_CONTINUE;
}

/* Output the first message
in the error descriptor. */
p=(MI_ERROR_DESC *)ius_info;
mi_error_sqlcode(p, &sqlcode);
mi_error_sql_state(p, sqlstate, 6);
mi_errmsg(p, mesg, sizeof(mesg)-1);
DPRINTF(trace_class, 1,
("error_callback: sqlstate=%s, sqlcode=%d, mesg=%s",
sqlstate, sqlcode, mesg));

/* Output the second message,
if one exists.  */
p=mi_error_desc_next((MI_ERROR_DESC *)ius_info);
if (p != NULL)
{
mi_error_sql_state(p, sqlstate, 6);
mi_error_sqlcode(p, &sqlcode);
mi_errmsg(p, mesg, sizeof(mesg)-1);
DPRINTF(trace_class, 1,
("error_callback: sqlstate=%s, sqlcode=%d, mesg=%s",
sqlstate, sqlcode, mesg));
}
else
{
DPRINTF(trace_class, 1,
("just one message in the error descriptor."));
}
return MI_CB_CONTINUE;
}
注意! 如果 UDR 象上面的 C 代码那样使用 mi_exec() 执行 SQL 语句,就必须用 variant 修饰符创建该 UDR。 即使 SQL 语句是从静态查询表进行选择并返回不变的结果,它仍然必须被创建为 variant

回页首

更多关于这个主题的信息

下列 IDN 技术说明提供了相关信息:

  • 对用于 DataBlade 模块的限制进行编码
  • 使用虚拟处理器的数据安全

回页首

词汇表

这篇技术说明中使用的首字母缩略词和词汇包括:

IDS
INFORMIX Dynamic Server。
UDR
用户定义的例程(User-defined routine)。
UDT
用户定义的类型(User-defined type)。

关于作者

Jean T. Anderson has authored this article

C UDR 的内存分配相关推荐

  1. C语言的变量的内存分配

    今晚看了人家写的一个关于C语言内存分配的帖子,发现真是自己想找的,于是乎就收藏了... 先看一下两段代码: char* toStr() {char *s = "abcdefghijkl&qu ...

  2. 释放变量所指向的内存_C++动态内存分配(学习笔记:第6章 15)

    动态内存分配[1] 动态申请内存操作符 new new 类型名T(初始化参数列表) 功能: 在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值. 结果值: 成功:T类型的指针,指向 ...

  3. linux内存分配 连续 足够,linux内存池能分配连续物理内存吗

    中. size参数: 内核是基于页技术分配内存,以最佳的利用系统的RAM. linux处理内存分配的方法是:创建一系列的内存对象池,每个池的内存大小事固定的,处理分配请求时,就直接在包含足够大的内存块 ...

  4. Linux创建线程时 内存分配的那些事

    文章目录 问题描述 问题分析 针对问题1 的猜测: 针对问题2 的猜测: 原理追踪 总结 问题描述 事情开始于一段内存问题,通过gperf工具抓取进程运行过程中的内存占用情况. 分析结果时发现一个有趣 ...

  5. Eigen向量化内存对齐/Eigen的SSE兼容,内存分配/EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    1.总结 对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐.对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而 ...

  6. 栈区和堆区内存分配区别

    一直以来总是对这个问题的认识比较朦胧,我相信很多朋友也是这样的,总是听到内存一会在栈上分配,一会又在堆上分配,那么它们之间到底是怎么的区别呢?为了说明这个问题,我们先来看一下内存内部的组织情况. 从上 ...

  7. Memcache内存分配策略

    转自:http://tank.blogs.tkiicpp.com/2010/12/14/memcache%e5%86%85%e5%ad%98%e5%88%86%e9%85%8d%e7%ad%96%e7 ...

  8. 利用TCMalloc替换Nginx和Redis默认glibc库的malloc内存分配

    TCMalloc的全称为Thread-Caching Malloc,是谷歌开发的开源工具google-perftools中的一个成员.与标准的glibc库的Malloc相比,TCMalloc库在内存分 ...

  9. linux环境内存分配原理

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  10. 可变分区存储管理实验报告总结_操作系统实验报告-可变分区存储管理方式的内存分配回收...

    一.实验目的 ( 1 )深入了解可变分区存储管理方式的内存分配回收的实现. 二.实验内容 编写程序完成可变分区存储管理方式的内存分配回收,要求有内存空间分配表, 并采用最优适应算法完成内存的分配与回收 ...

最新文章

  1. 微信sdk服务器支付文档,微信支付-普通下单开发者文档
  2. 关于owa over https的邮箱加密访问
  3. jQuery 判断是否包含某个属性
  4. webService(一)开篇
  5. matlab18a安装步骤,MATLAB R2018a图文安装教程
  6. 矩阵相关定义性质全总结
  7. 批量下载花瓣图片脚本--抖机灵
  8. fot mac matlab_Matlab for Mac
  9. 微信小程序调查问卷避坑
  10. 添加五笔输入法(默认的)windows sever 2012 r2
  11. 计算机主机与显示屏如何接线,主机跟显示器怎么连接
  12. 我为什么鼓励工程师写博客
  13. SQL——查询和1002号的同学学习的课程完全相同的其他同学的学号和姓名
  14. Python语言(实践)练习题——函数及代码复用
  15. SpringBoot打开resources目录下的文件操作
  16. 计算机科学与技术补中益气丸的成分,经典名方,补中益气丸运用解析
  17. 【C++学习笔记】类型转换和跳转语句
  18. EndNote技巧 | Endnote导出文献
  19. 网页会员文档查看方法
  20. 华为路ws5200设置虚拟服务器,华为路由器WS5200如何设置上网 最详细的华为路由器WS5200上网设置方法教程...

热门文章

  1. 电阻式触摸屏的工作原理
  2. 文章-编程需要知道多少数学知识?
  3. 如何重置网络?如何激活产品?
  4. CXK, 出来打球!
  5. 怎么样对阿里云ECS主机进行绑定域名
  6. C语言 —— int32_t uint32_t 及size_t
  7. Python爬虫之堆糖网图片(二)
  8. Java程序员的五个职业发展方向
  9. windows图片和传真查看器打不开的解决办法
  10. 多线程——start()和run()