并发与线程安全

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

与此有些相关的是,来自单个线程或多个线程的多步操作的重叠/并发执行问题。例如,可以从同一个 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 一致。