[英]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 foo1
鎖mutex1
雖然編輯索引124 foo2
希望編輯索引2349,它不能直到foo1
釋放mutex1
。
有沒有辦法可以將鎖定互斥鎖的爭用轉移到元素級別? 意思是:我希望foo2
和foo1
只在同一個互斥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 10000
和numThreads 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.