并发性和线程安全性

通常,对象允许来自多个线程的序列化访问:一个线程可以进行调用,完成后,另一个线程可以进行调用。它们不允许来自多个线程的并发访问。

与此有点相关的是,来自单个线程或多个线程的多步操作的重叠/并发执行问题。例如,可以从同一个 AdbcConnection 创建两个 AdbcStatement 对象。

struct AdbcStatement stmt1;
struct AdbcStatement stmt2;

struct ArrowArrayStream out1;
struct ArrowArrayStream out2;

/* Ignoring error handling for brevity */
AdbcStatementNew(&conn, &stmt1, NULL);
AdbcStatementNew(&conn, &stmt2, NULL);
AdbcStatementSetSqlQuery(&stmt1, "SELECT * FROM a", NULL);
AdbcStatementSetSqlQuery(&stmt2, "SELECT * FROM b", NULL);

AdbcStatementExecuteQuery(&stmt1, &out1, NULL, NULL);
AdbcStatementExecuteQuery(&stmt2, &out2, NULL, NULL);
/* What happens to the result set of stmt1? */

如果客户端应用程序在 stmt1 上调用 AdbcStatementExecuteQuery(),然后在 stmt2 上调用,而没有读取 stmt1 的结果集,会发生什么?一些现有的客户端库/协议,如 libpq,不支持来自单个连接的查询并发执行。因此,驱动程序必须:1) 在第一次 Execute 期间将所有结果缓冲到内存中(或以其他方式允许程序继续读取第一个结果集);2) 在第二次 Execute 时发出错误;或者 3) 在第二次 Execute 时使第一个语句的结果集失效。

在这种情况下,ADBC 允许驱动程序选择 1) 或 2)。如果可能且合理,驱动程序应允许并发执行,无论是由于底层协议为此而设计还是通过缓冲结果集。但如果不可能支持,驱动程序可以报错。

另一个用例是只有一个语句,但多次执行它并并发读取结果集。例如,客户端可能希望使用预编译语句来执行此操作。

/* Ignoring error handling for brevity */
struct AdbcStatement stmt;
AdbcStatementNew(&conn, &stmt, NULL);
AdbcStatementSetSqlQuery(&stmt, "SELECT * FROM a WHERE foo > ?", NULL);
AdbcStatementPrepare(&stmt, NULL);

struct ArrowArrayStream stream;
AdbcStatementBind(&stmt, &array1, &schema, NULL);
AdbcStatementExecuteQuery(&stmt, &stream, NULL, NULL);
/* Spawn a thread to process `stream` */

struct ArrowArrayStream stream2;
AdbcStatementBind(&stmt, &array2, &schema, NULL);
AdbcStatementExecuteQuery(&stmt, &stream2, NULL, NULL);
/* What happens to `stream` here? */

ADBC 选择不允许这样做(具体来说:第二次调用 Execute 必须使第一次调用的结果集失效),这与通常不支持这种方式“重叠”使用单个预编译语句的现有 API 一致。