Multithreading SWI Prolog:使用带互斥锁的线程

Multithreading SWI Prolog:使用带互斥锁的线程,multithreading,prolog,swi-prolog,Multithreading,Prolog,Swi Prolog,我正在努力适应Prolog的线程库。我知道它是基于C的线程实现的,对此我相当熟悉。在C语言中使用互斥锁的一个简单例子是对共享资源执行一些数学运算,因为不使用互斥锁来锁定修改原始共享资源的原子指令会导致由竞争条件引起的随机行为。我试图在Prolog中编写相同的示例,但我的程序只是挂起,我不太确定如何像在C中一样使用共享资源参数。下面是我的代码: mutex_create(mtx1). add_2(X,D):-number(X),mutex_lock(mtx1),D is X+2,mutex_un

我正在努力适应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),mutex_lock(mtx1),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).
使用trace,我可以看到我的代码在尝试加入使用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, _10024), <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, _10036), <thread>(19,0x559ab291d730)) ? 
   Call: (1) sub_3(_8, _10) ? creep
   Call: (9) thread_join(<thread>(18,0x559ab291d1e0)) ? creep
[trace]?-main(5)。
电话:(8)主(5)?爬行
^调用:(9)线程\u创建(添加\u 2(5,\u 10024),\u 10044)?爬行
^退出:(9)创建线程(用户:添加线程2(5,_10024),(18,0x559ab291d1e0))?
电话:(1)添加2(5,_10)?爬行
^调用:(9)线程创建(子线程3(\u 10024,\u 10036),\u 10056)?爬行
^退出:(9)线程创建(用户:sub_3(10024,10036),(19,0x559ab291d730))?
电话:(1)sub_3(_8,_10)?爬行
调用:(9)线程连接((18,0x559ab291d1e0))?爬行

我希望每个线程都运行,最终每次输出的主(M)谓词是4。

您需要调用
mutex\u 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),mutex_lock(mtx1),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), ID_1),
            thread_create(sub_3(shared), ID_2),
            thread_join(ID_1),
            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, 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),
        (   flag(Shared, Number, Number),
            NewNumber is Number - 3,
            flag(Shared, _, NewNumber) ),
        mutex_unlock(my_mutex) ),
    writeln(Number -> NewNumber).
结果:

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

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

thread\u create/2创建给定目标的一个副本,一个变量 将从线程绑定到主线程 永不归还。或如文件所述:

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

因此@slago的解决方案可能反映了OPs的意图。但应该注意的是,SWI Prolog已经实现了。使用此元谓词,@slago的代码可以简化:

add_2(Shared) :-
    with_mutex(my_mutex,
        (   flag(Shared, Number, Number),
            NewNumber is Number + 2,
            flag(Shared, _, NewNumber) )),
    writeln(Number -> NewNumber).

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

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

你可能需要
mutex_unlock
而不是
mutex_lock
结尾的
add_2
sub_3
啊,是的,我犯了愚蠢的语法错误。但是,即使将第二个lock调用修复为unlock,程序仍然挂起。调用trace会发现相同的错误,程序仍然挂起。不确定发生这种情况的原因。您的示例无法真正并发执行,因为必须先完成
add_2
,然后才能执行
sub_3
。请注意,
sub\u 3
中的
number(Y)
将在Y未实例化的情况下失败。使用setup\u call\u cleanup/3执行互斥操作是否更安全?(现在是在序言中的时候了吗?这是有道理的。)啊,我明白了!所以本质上,除非它显式标记,PROlog的线程总是会从我的主线程上执行不同的实例化变量,并且不以与C或C++线程的方式相同的方式运行WRT共享资源。在这种情况下,setup\u call\u cleanup/1具体做什么?目标
setup\u call\u cleanup(setup,goal,cleanup)
调用
(一次(setup),goal)
。如果
设置
成功,
清理
将在
目标
完成后准确调用一次:失败、确定性成功、提交或异常。