在多处理共享类型数组

时间:2017-08-15 13:33:18

标签: python arrays string ctypes

我正在尝试使用多处理模块的sharedctypes部分在进程之间共享一些字符串。

TL; DR: 我希望将我的字符串放入sharedctypes数组中,如下所示:

from multiprocessing.sharedctypes import Array

Array(ctypes.c_char, ['a string', 'another string'])

更多信息:

docs有这样的说明:

请注意,ctypes.c_char数组具有值和原始属性,允许用户存储和检索字符串。

单独使用c_char

from multiprocessing.sharedctypes import Array

Array(ctypes.c_char, ['a string', 'another string'])

我收到类型错误,这是有道理的:

TypeError: one character bytes, bytearray or integer expected

通过将sting分成字节(这也很有意义),这可以(种类)起作用:

from multiprocessing.sharedctypes import Array

multiproccessing.sharedctypes.Array(ctypes.c_char, [b's', b't', b'r', b'i', b'n', b'g'])

但这对于存储大型字符串列表并不是很方便。

但是,当我尝试使用文档here中显示并在 note 中提及的valueraw属性时,仍然没有魔法:

Array(ctypes.c_char.value, ['string'])

给出了这个错误:

TypeError: unsupported operand type(s) for *: 'getset_descriptor' and 'int'

raw给出了这个:

Array(ctypes.c_char.raw, ['string'])

AttributeError: type object 'c_char' has no attribute 'raw'

我还尝试使用c_wchar_p类型,它在原始C兼容数据类型表中(在docs中找到)直接对应于字符串:

 Array(ctypes.c_wchar_p, ['string'])

这个 CRASHES python,没有报告错误代码,该过程只是以代码0退出。

为什么sharedctypes数组不能保存像c_wchar_p类型的指针?关于如何在共享类型数组中存储字符串的任何其他解决方案或建议都是非常受欢迎的!

更新 - 此代码偶尔会有效(大部分时间python都会停止工作,但偶尔我会收回字符串,尽管它们大多是乱码)。但评论中提到它在Windows上运行良好。

from multiprocessing import Process, Lock
from multiprocessing.sharedctypes import Value, Array
import ctypes


def print_strings(S):
    """Print strings in the C array"""
    print([a for a in S])

if __name__ == '__main__':
    lock = Lock()
    string_array = Array(ctypes.c_wchar_p, ['string'])
    q = Process(target=print_strings, args=(string_array,))
    q.start()
    q.join()

更新2

这是我得到的胡言乱语:

['汣猎癞汥⁹景椠瑮搠祴数\ u2e73ਊ††敓\ u2065汁潳\ u200a†ჰⴭⴭⴭਭ††捳滟\ u2e79灳捥慩\ u2e6c癞\ u202c捳滟\ u2e79灳捥慩\ u2e6c癞\ u0a65 \ u200a†丠琅獥\ u200a†ჰⴭⴭ\ u200a†圠\ u2065猎\ u2065桴\ u2065污潧楲桴\ u206d异汢獩敨\ u2064祢䌠敬狝慨⁷ㅛ彝愠摮爠晥牥湥散\ u2064祢\ u200a†䄠牢浡睯莹⁺湡\ u2064瑓来湵嬠崲\ u2c5f映牯眠桢档琠敨映湵琐潩\ u206e润慭湩椠ੳ††慰玱莹潩敮\ u2064湩潴琠敨琠潷椠瑮牥庆獬嬠ⰰ崸愠摮⠠ⰸ湩⥦\ u202c湡\ u2064桃扥獹敨\ u0a76††溃祬潮业污攠灸湡楳汤\ u2073牡\ u2065浥汰祯摥椠\ u206e慥档椠瑮牥庆\ u2e6c删汥瑡癞\ u2065牥潲\ u2072汤\ u200a†琠敨搠浯楡\ u206eせ㌬崰甠楳杮䤠䕅⁅牡莹浨瑥捩椠\ u2073润畣敭瑮摥嬠崳\ u205f獡栠痴湩\ u2067 \ u0a61††数欢漠\u2066⸵攸ㄭ“楷桴愠\ u206e浲\ u2073景ㄠ㐮ⵥ㘱⠠\u206e‽〳 〰⤰ਮ\ u200a†删晥牥湥散ੳ††ⴭⴭⴭⴭⴭ\ u200a†⸠\ u202eㅛ⁝\ u2e43 \ u202e汃湥桳睡\ u202c䌢敨祢桳症猠牥敩\ u2073潦\ u2072慭桴浥瑡捩污映湵琐潩狝Ⱒ椠8†††††⨠慎楴汤污倠票楳惯\ u206c慌潢慲潴祲䴠瑡敨慭楴惯\ u206c慔汢獥Ⱚ瘠汯\ u202eⰵ䰠汤润㩮\ u200a†††††效\ u2072愠敪瑳❹\ u2073瑓瑡潩敮祲传晦捩ⱥ ㄠ㘹⸲\ u200a†⸠\u202e㉛⁝\ u2e4d䄠牢浡睯莹⁺湡\ u2064 \ u2e49䄠\ u202e瑓来湵\ u202c䠪湡扤浔\ u206b景䴠瑡敨慭楴惯6†††††䘠湵琐潩狝Ⱚㄠ琰\ u2068牰湩楴杮\ u202c敎⁷沩岁>漱敶Ⱳㄠ㘹ⰴ潆\u2e70㌠㤷ਮ†††††栠瑴㩰⼯睷\ u2e77慭桴献畦挮⽡捾浢愯湡獤瀯条彦㜳⸹瑨7††⸮嬠崳栠瑴㩰⼯潫敢敳牡档挮慰\ u2e6e牯⽧瑨润獣䴯瑡\ u2d68暋桰獥䴯瑡⽨暋桰獥栮浴6 \ u200a†䔠慸灭敬ੳ††ⴭⴭⴭⴭ\ u200a†㸠㸾渠\ u2e70ど嬨⸰⥝\ u200a†愠牲祡ㄨ〮\ u0a29††㸾〜灮椮⠰せⰮㄠ\ u202e \ u202b樲⥝\ u200a†愠牲祡嬨ㄠ〮〰〰〰⬰⸰\ u206a†††Ⱐ†⸰㠱㠷㌵㌷ 〫㘮㘴㘱㐹⥝ਊ††','ਊ††u \ u2065汁潳\ u200a†††††滟滟\ u2e79灳捥慩\ u2e6c癞\ u202c捳滟\ u2e79灳捥慩\ u2e6c癞\ u0a65 \ u200a†丠琅獥\ u200a†ჰⴭⴭ\ u200a†圠\ u2065猎\ u2065桴\ u2065污潧楲桴\ u206d异汢獩敨\ u2064祢䌠敬狝慨⁷ㅛ彝愠摮爠晥牥湥散\ u2064祢\ u200a†䄠牢浡睯莹⁺湡\ u2064瑓来湵嬠崲\ u2c5f映牯眠桢档琠敨映湵琐潩\ u206e润慭湩椠ੳ††慰玱莹潩敮\ u2064湩潴琠敨琠潷椠瑮牥庆獬嬠ⰰ崸愠摮⠠ⰸ湩⥦\ u202c湡\ u2064桃扥獹敨\ u0a76††溃祬潮业污攠灸湡楳汤\ u2073牡\ u2065浥汰祯摥椠\ u206e慥档椠瑮牥庆\ u2e6c删汥瑡癞\ u2065牥潲\ u2072汤\ u200a†琠敨搠浯楡\ u206eせ㌬崰甠楳杮䤠䕅⁅牡莹浨瑥捩椠\ u2073润畣敭瑮摥嬠崳\ u205f獡栠痴湩\ u2067 \ u0a61††数欢漠\u2066⸵攸ㄭ“楷桴愠\ u206e浲\ u2073景ㄠ㐮ⵥ㘱⠠\u206e‽〳〰⤰ਮ\ u200a†删晥牥湥散ੳ††ⴭⴭⴭⴭⴭ\ u200a†⸠\ u202eㅛ⁝\ u2e43圠\ u202e汃湥桳睡\ u202c䌢敨祢桳症猠牥敩\ u2073潦\ u2072慭桴浥瑡捩污映湵琐潩狝Ⱒ椠8†††††⨠慎楴汤污倠票楳惯\ u206c慌潢慲潴祲䴠瑡敨慭楴惯\ u206c慔汢獥Ⱚ瘠汯\ u202eⰵ䰠汤润㩮\ u200a†††††效\ u2072愠敪瑳❹\ u2073瑓瑡潩敮祲传晦捩ⱥㄠ㘹⸲ \ u200a†⸠\u202e㉛⁝\ u2e4d䄠牢浡睯莹⁺湡\ u2064 \ u2e49䄠\ u202e瑓来湵\ u202c䠪湡扤浔\ u206b景䴠瑡敨慭楴惯6†††††䘠湵琐潩狝Ⱚㄠ琰\ u2068牰湩楴杮\ u202c敎⁷沩岁>漱敶Ⱳㄠ㘹ⰴ潆\u2e70㌠㤷ਮ†††††栠瑴㩰⼯睷\ u2e77慭桴献畦挮⽡捾浢愯湡獤瀯条彦㜳⸹瑨7††⸮嬠崳栠瑴㩰⼯潫敢敳牡档挮慰\ u2e6e牯⽧瑨润獣䴯瑡\ u2d68暋桰獥䴯瑡⽨暋桰獥栮浴6 \ u200a†䔠慸灭敬ੳ††ⴭⴭⴭⴭ\ u200a†㸠㸾渠\ u2e70ど嬨⸰⥝\ u200a†愠牲祡ㄨ〮\ u0a29††㸾〜灮椮⠰せⰮㄠ\ u202e \ u202b樲⥝\ u200a †愠牲祡嬨ㄠ〮〰〰〰⬰⸰\ u206a†††Ⱐ†⸰㠱㠷㌵㌷〫㘮㘴㐹樴⥝ਊ††']

(是的,显然所有人都来自'字符串',不要问我怎么样)

2 个答案:

答案 0 :(得分:2)

the documentation中提到了您遇到的问题:

  

注意:虽然可以将指针存储在共享内存中,但请记住这将引用特定进程的地址空间中的某个位置。但是,指针很可能在第二个进程的上下文中无效,并且尝试从第二个进程取消引用指针可能会导致崩溃。

这意味着存储指针(如字符串)不起作用,因为只有地址才会到达子进程,并且该地址将不再有效(因此分段错误)。例如,考虑这个替代方案,其中所有字符串连接成一个数组,另一个长度的数组也被传递(你可以调整它以方便):

from multiprocessing import Process, Lock
from multiprocessing.sharedctypes import Value, Array
import ctypes

def print_strings(S, S_len):
    """Print strings in the C array"""
    received_strings = []
    start = 0
    for length in S_len:
        received_strings.append(S[start:start + length])
        start += length
    print("received strings:", received_strings)

if __name__ == '__main__':
    lock = Lock()
    my_strings = ['string1', 'str2']
    my_strings_len = [len(s) for s in my_strings]
    string_array = Array(ctypes.c_wchar, ''.join(my_strings))
    string_len_array = Array(ctypes.c_uint, my_strings_len)
    q = Process(target=print_strings, args=(string_array, string_len_array))
    q.start()
    q.join()

输出:

received strings: ['string1', 'str2']

关于子流程中的地址:

这个问题有点偏僻,但是很长时间才能发表评论。老实说,这开始超出我的深度,请看下面eryksun的评论,以获得更明智的见解,但无论如何,这是我的理解。在Unix(类似)上,通过fork创建的新进程具有与父进程相同的内存和(虚拟)地址,但是如果您然后exec某些程序不再是这种情况;我不知道Python的multiprocessing是否在Unix上运行exec(注意:请参阅eryksun's comment了解更多关于此问题和set_start_method),但无论如何我都不会假设可以保证Python管理的内存池中的任何地址都应该保持不变。在Windows上,CreateProcess从可执行文件创建一个新进程,该进程在原则上与父进程没有任何共同之处。我不认为多个进程(.so / .dll)使用的共享库应该位于任一平台的同一地址。我不认为在使用共享内存时,进程之间的共享(虚拟)地址甚至是有意义的,因为如果我没记错(我可能没有),共享内存块将映射到每个进程上的任意虚拟地址。所以我的印象是没有充分的理由(或者至少是“好的和明显的”)与子进程共享地址(当然,ctypes中的指针类型仍然可以与同一个本地库进行对话处理)。

正如我所说,我对此并不是100%有信心,但我认为总的想法就是这样。

答案 1 :(得分:1)

.raw.value生效的其他示例。每个文档仅适用于Array(ctypes.c_char,...)

from multiprocessing import Process
from multiprocessing.sharedctypes import Value, Array
import ctypes

def print_strings(s):
    """Print strings in the C array"""
    print(s.value)
    print(len(s))
    s[len(s)-1]=b'x'

if __name__ == '__main__':
    string_array = Array(ctypes.c_char, b'string')
    q = Process(target=print_strings, args=(string_array,))
    q.start()
    q.join()
    print(string_array.raw)

输出显示共享缓冲区已被修改:

b'string'
6
b'strinx'