SWI-Prolog:使用线程和互斥锁

问题描述

我正在尝试习惯 Prolog 的线程库。我知道它基于 C 的线程实现,我对此非常熟悉。在 C 中使用互斥锁的一个简单示例是对共享资源进行一些数学运算,因为不互斥锁对修改原始共享资源的原子指令会导致竞争条件引起的随机行为。我正在尝试在 Prolog 中编写相同的示例,但我的程序只是挂起,我不太确定如何像在 C 中一样使用共享资源参数。这是我的代码

mutex_create(mtx1).

add_2(X,D):-number(X),mutex_lock(mtx1),D is X+2,mutex_unlock(mtx1).

sub_3(Y,D):-number(Y),D is Y-3,mutex_unlock(mtx1).

main(M):-
    thread_create(add_2(M,A),ID_1),thread_create(sub_3(A,F),ID_2),thread_join(ID_1),thread_join(ID_2),writeln(F).

使用跟踪,我可以看到我的代码在尝试加入使用 add_2 谓词的 ID_1 线程时挂起,但我不确定为什么:

[trace]  ?- main(5).
   Call: (8) main(5) ? creep
^  Call: (9) thread_create(add_2(5,_10024),_10044) ? creep
^  Exit: (9) thread_create(user:add_2(5,<thread>(18,0x559ab291d1e0)) ? 
   Call: (1) add_2(5,_10) ? creep
^  Call: (9) thread_create(sub_3(_10024,_10036),_10056) ? creep
^  Exit: (9) thread_create(user:sub_3(_10024,<thread>(19,0x559ab291d730)) ? 
   Call: (1) sub_3(_8,_10) ? creep
   Call: (9) thread_join(<thread>(18,0x559ab291d1e0)) ? creep

我希望发生的是每个线程都运行,最终我的 main(M) 谓词每次打印出来的是 4。

解决方法

您需要调用 mutex_create/1 谓词,而不是尝试为其定义事实。试试:

:- initialization(mutex_create(mtx1)).

add_2(X,D):-number(X),mutex_lock(mtx1),D is X+2,mutex_unlock(mtx1).

sub_3(Y,D):-number(Y),D is Y-3,mutex_unlock(mtx1).

main(M):-
    thread_create(add_2(M,A),ID_1),thread_create(sub_3(A,F),ID_2),thread_join(ID_1),thread_join(ID_2),writeln(F).

但请注意,调用 main/1 仍然会失败,如@gusbro 的评论中所述。

,

在 SWI-Prolog 中,一个可能的解决方案是:

main(Number) :-
    flag(shared,_,Number),% Number is a shared data
    setup_call_cleanup(
        mutex_create(Mutex,[alias(my_mutex)]),(   thread_create(add_2(shared),thread_create(sub_3(shared),thread_join(ID_2) ),mutex_destroy(Mutex) ),flag(shared,NewNumber,NewNumber),writeln(shared: NewNumber).

add_2(Shared) :-
    setup_call_cleanup(
        mutex_lock(my_mutex),(   flag(Shared,Number,NewNumber is Number + 2,flag(Shared,NewNumber) ),mutex_unlock(my_mutex) ),writeln(Number -> NewNumber).

sub_3(Shared) :-
    setup_call_cleanup(
        mutex_lock(my_mutex),NewNumber is Number - 3,writeln(Number -> NewNumber).

结果:

?- main(5).
5->7
7->4
shared:4
true.

PS 根据文档“Prolog 线程有自己的堆栈,并且只共享 Prolog 堆:谓词、记录、标志和其他全局不可回溯的数据。

,

thread_create/2 创建给定目标的副本,一个变量 从属线程绑定到主线程 永远不会被退回。或者如文档所述:

目标参数被复制到新的 Prolog 引擎。这意味着在任一线程中进一步实例化该术语不会对另一个线程产生影响:Prolog 线程不共享其堆栈中的数据。

https://www.swi-prolog.org/pldoc/man?predicate=thread_create/3

所以@slago 的解决方案可能反映了 OP 的意图。但需要注意的是,SWI-Prolog 有 with_mutex/2。使用这个元谓词可以简化@slago 的代码:

add_2(Shared) :-
    with_mutex(my_mutex,NewNumber) )),writeln(Number -> NewNumber).

sub_3(Shared) :-
    with_mutex(
        (   flag(Shared,writeln(Number -> NewNumber).

with_mutex/2 适用于第二个参数是半确定性的。 Jekejeke Prolog 有 with_lock/2,它允许第二个参数为 multi。