簡體   English   中英

如何為數組中的元素鎖定MUTEX,而不是為完整的數組鎖定

[英]How can i lock a MUTEX for an element in the array, not for the complete array

問題的簡短版本:我有2個共享同一個數組的函數,當一個編輯它時,另一個是讀它。 但是,向量很長(5000個樣本),很少發生並發訪問。 MUTEX1上的Mutex爭用正在減慢程序的速度。

如何鎖定內存的某些位置而不是完整的塊以減少爭用?

編輯:注意:我必須盡可能使用更新的G值。

EDIT2:例如,我有長度5000的排列G foo1mutex1雖然編輯索引124 foo2希望編輯索引2349,它不能直到foo1釋放mutex1

有沒有辦法可以將鎖定互斥鎖的爭用轉移到元素級別? 意思是:我希望foo2foo1只在同一個互斥foo1競爭,只有當他們想要編輯相同的索引時。 例如: foo1想要編輯索引3156,而foo2想要編輯索引3156。

帶代碼說明的長版本:我正在為復雜的數學函數編寫代碼,我使用pthreads來並行代碼並提高性能。 代碼非常復雜,我可以發布它,但我可以將模型發布到代碼中。

基本上我有2個數組,我想用2個並行運行的線程編輯。 一個線程運行foo1 ,另一個運行foo2 但是,它們應該以特定的順序運行,並使用mutex_B_A1_A2 )來控制序列。 它如下:

foo1 (first half)
foo2 (first half) and foo1 (second half) (in parallel)
foo1 (first half) and foo2 (second half) (in parallel)
...
foo2(second half)

然后我會檢索我的結果。 foo1的前半部分,我將使用G1中可能由foo2同時編輯的結果。 因此我使用Mutex1來保護它。 同樣發生在foo2用於G 但是,將完整向量鎖定為1值非常有效,它們幾乎不會同時編輯相同的內存位置。 當我比較結果時,它幾乎總是一樣的。 我想要一種方法一次鎖定一個元素,這樣他們只會競爭相同的元素。

我將為有興趣知道它如何工作的人描述代碼:

#include <pthread.h>
#include <iostream>

using namespace std;

#define numThreads 2
#define Length 10000

pthread_t threads[numThreads];

pthread_mutex_t mutex1   = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t Mutex_B  = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t Mutex_A1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t Mutex_A2 = PTHREAD_MUTEX_INITIALIZER;

struct data_pointers
{
    double  *A;
    double  *B;
    double  *G;
    double  *L;
    int idxThread;
};

void foo1   (data_pointers &data);
void foo2   (data_pointers &data);

void *thread_func(void *arg){
    data_pointers data = *((data_pointers *) arg);
    if (data.idxThread==0)
        foo1 (data);
    else
        foo2 (data);
}

到目前為止,它是定義和線程調用函數,我記得我定義了Length 10000numThreads 2

void foo1 ( data_pointers &data)
{
    double *A           = data.A;
    double *L           = data.L; 
    double *G           = data.G; 
    double U;

    for (int ijk =0;ijk<5;ijk++){
        /* here goes some definitions*/

        pthread_mutex_lock(&Mutex_A1);

        for (int k =0;k<Length;k++){
            pthread_mutex_lock(&mutex1); 
            U = G[k];
            pthread_mutex_unlock(&mutex1);
            /*U undergoes a lot of mathematical operations here


            */
        }

        pthread_mutex_lock(&Mutex_B);
        pthread_mutex_unlock(&Mutex_A2);
        for (int k =0;k<Length;k++){
            /*U another mathematical operations here


            */
            pthread_mutex_lock(&mutex1);
            L[k] = U;
            pthread_mutex_unlock(&mutex1);
            pthread_mutex_unlock(&Mutex_B);
        }
    }
}

在foo1中我鎖定了mutexA1並完成了我的工作,然后我鎖定了MutexB並解鎖了MutexA2因此foo2可以開始工作。 請注意, main通過鎖定MutexA2開始。 這樣我保證foo1開始下半場用mutexB鎖定,這樣一來, foo2無法進入函數的下半部分直到foo1解鎖mutexB

void foo2 (data_pointers &data)
{
    double *A           = data.A;
    double *L           = data.L; 
    double *G           = data.G; 
    double U;

    for (int ijk =0;ijk<5;ijk++){
        /* here goes some definitions*/

        pthread_mutex_lock(&Mutex_A1);

        for (int k =0;k<Length;k++){
            pthread_mutex_lock(&mutex1); 
            U = G[k];
            pthread_mutex_unlock(&mutex1);
            /*U undergoes a lot of mathematical operations here


            */
        }

        pthread_mutex_lock(&Mutex_B);
        pthread_mutex_unlock(&Mutex_A2);
        for (int k =0;k<Length;k++){        
            /*U another mathematical operations here


            */
            pthread_mutex_lock(&mutex1);
            L[k] = U;
            pthread_mutex_unlock(&mutex1);
            pthread_mutex_unlock(&Mutex_B);

        }
    }
}

現在,當foo1解鎖mutexB它必須等待foo2解鎖mutexA1以便它可以正常工作, foo2只會解鎖mutexA2因為它已經解鎖了mutexB

這種情況持續了5次。

int main(){
    double G1[Length];
    double G2[Length];
    double B1[Length];
    double B2[Length];
    double A2[Length];
    double A1[Length];
    data_pointers data[numThreads];

    data[0].L           = G2;
    data[0].G           = G1;   
    data[0].A           = A1;
    data[0].B           = B1;
    data[0].idxThread   = 0;

    data[1].L           = G1;
    data[1].G           = G2;   
    data[1].A           = A2;
    data[1].B           = B2;
    data[1].idxThread   = 1;

    pthread_mutex_lock(&Mutex_A2);

    pthread_create(&(threads[0]), NULL, thread_func, (void *) &(data[0]));
    pthread_create(&(threads[1]), NULL, thread_func, (void *) &(data[1]));
    pthread_join(threads[1], NULL);
    pthread_join(threads[0], NULL);

    pthread_mutex_unlock(&Mutex_A1);
    pthread_mutex_unlock(&Mutex_A2);

    return 0;
}

請注意,這只是一個示例代碼。 編譯並按預期工作,但沒有輸出。

最后編輯:謝謝大家的好主意,我有很多經驗,並樂於遵循這些建議。 我將對所有答案進行投票,因為它們很有用,並選擇最接近原始問題(原子性)

如果不調整數組大小,則不需要在單個元素或整個數組上使用任何互斥鎖。

原子地閱讀你的價值觀,原子地寫下你的價值觀並保持冷靜。

如果您希望在不使用互斥鎖的情況下對類似數組的數據結構進行高性能多線程訪問,則可以研究比較和交換。 也許您可以設計一個適用於您特定問題的無鎖數據結構。 https://en.wikipedia.org/wiki/Compare-and-swap

關於發布的代碼,似乎你的問題有點太復雜了。 如果你想實現:

foo1 (first half)
foo2 (first half) and foo1 (second half) (in parallel)
foo1 (first half) and foo2 (second half) (in parallel)
...
foo2(second half)

兩個mutx應該做。

也許這可以。 下面有一些偽代碼:

// These global variables controls which thread is allowed to
// execute first and second half.
// 1 --> Foo1 may run
// 2 --> Foo2 may run
int accessFirstHalf = 1;
int accessSecondHalf = 1;

void foo1 ( data_pointers &data)
{
    while(YOU_LIKE_TO_GO_ON)
    {
        while (true)
        {
            TAKE_MUTEX_FIRST_HALF;
            if (accessFirstHalf == 1)
            {
                RELEASE_MUTEX_FIRST_HALF;
                break;
            }
            RELEASE_MUTEX_FIRST_HALF;
            pthread_yield();
        }

        // Do the first half

        TAKE_MUTEX_FIRST_HALF;
        // Allow Foo2 to do first half
        accessFirstHalf == 2;
        RELEASE_MUTEX_FIRST_HALF;

        while (true)
        {
            TAKE_MUTEX_SECOND_HALF;
            if (accessSecondHalf == 1)
            {
                RELEASE_MUTEX_SECOND_HALF;
                break;
            }
            RELEASE_MUTEX_SECOND_HALF;
            pthread_yield();
        }

        // Do the second half

        TAKE_MUTEX_SECOND_HALF;
        // Allow Foo2 to do second half
        accessSecondHalf == 2;
        RELEASE_MUTEX_SECOND_HALF;
    }
}


void foo2 ( data_pointers &data)
{
    while(YOU_LIKE_TO_GO_ON)
    {
        while (true)
        {
            TAKE_MUTEX_FIRST_HALF;
            if (accessFirstHalf == 2)
            {
                RELEASE_MUTEX_FIRST_HALF;
                break;
            }
            RELEASE_MUTEX_FIRST_HALF;
            pthread_yield();
        }

        // Do the first half

        TAKE_MUTEX_FIRST_HALF;
        // Allow Foo1 to do first half
        accessFirstHalf == 1;
        RELEASE_MUTEX_FIRST_HALF;

        while (true)
        {
            TAKE_MUTEX_SECOND_HALF;
            if (accessSecondHalf == 2)
            {
                RELEASE_MUTEX_SECOND_HALF;
                break;
            }
            RELEASE_MUTEX_SECOND_HALF;
            pthread_yield();
        }

        // Do the second half

        TAKE_MUTEX_SECOND_HALF;
        // Allow Foo1 to do second half
        accessSecondHalf == 1;
        RELEASE_MUTEX_SECOND_HALF;
    }
}


int main()
{
    // start the threads with foo1 and foo2
}

使用原子指針“鎖定”內存中某些位置的示例代碼:

#include <vector>
#include <atomic>
#include <thread>

using container = std::vector<std::atomic<double>>;
using container_size_type = container::size_type;

container c(300);

std::atomic<container::pointer> p_busy_elem{ nullptr };

void editor()
{
    for (container_size_type i{ 0 }, sz{ c.size() }; i < sz; ++i)
    {
        p_busy_elem.exchange(&c[i]); // c[i] is busy
        // ... edit c[i] ... // E: calculate a value and assign it to c[i]
        p_busy_elem.exchange(nullptr); // c[i] is no longer busy
    }
}

void reader()
{
    for (container_size_type i{ 0 }, sz{ c.size() }; i < sz; ++i)
    {
        // A1: wait for editor thread to finish editing value
        while (p_busy_elem == &c[i])
        {
            // A2: room a better algorithm to prevent blocking/yielding
            std::this_thread::yield();
        }

        // B: if c[i] is updated in between A and B, this will load the latest value
        auto value = c[i].load();

        // C: c[i] might have changed by this time, but we had the most up to date value we could get without checking again
        // ... use value ...
    }
}

int main()
{
    std::thread t_editor{ editor };
    std::thread t_reader{ reader };
    t_editor.join();
    t_reader.join();
}

在編輯器線程中,忙指針被設置為指示當前正在編輯該存儲器位置( E )。 如果線程B在設置忙指針后嘗試讀取該值,它將等到編輯完成后再繼續( A1 )。

關於A2的注釋:可以在這里放置更好的系統。 可以保留嘗試讀取時忙碌的節點列表,然后我們將i添加到該列表並嘗試稍后處理列表。 好處:循環可能被告知執行continue和指數過去當前正在編輯i會被讀取。

要讀取的值的副本( B ),以便使用它( C ),但需要。 這是我們最后一次檢查c[i]的最新值。

這似乎是您要求的核心:

 foo1 (first half) foo2 (first half) and foo1 (second half) (in parallel) foo1 (first half) and foo2 (second half) (in parallel) ... foo2(second half) 

實現與pthreads交錯的最簡單方法是使用障礙。

使用pthread_barrier_init()使用count 2初始化障礙foo1()然后執行:

first half
pthread_barrier_wait()
second half
pthread_barrier_wait()
...
first half
pthread_barrier_wait()
second half
pthread_barrier_wait()

foo2()執行一個稍微不同的序列:

pthread_barrier_wait()
first half
pthread_barrier_wait()
second half
....
pthread_barrier_wait()
first half
pthread_barrier_wait()
second half

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM