mirror of https://github.com/grpc/grpc.git
Merge pull request #20119 from markdroth/c++_mpscq
Convert mpscq API from C to C++.pull/15577/head^2
commit
d1bca6ff92
31 changed files with 438 additions and 410 deletions
@ -1,117 +0,0 @@ |
|||||||
/*
|
|
||||||
* |
|
||||||
* Copyright 2016 gRPC authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
* |
|
||||||
*/ |
|
||||||
|
|
||||||
#include <grpc/support/port_platform.h> |
|
||||||
|
|
||||||
#include "src/core/lib/gpr/mpscq.h" |
|
||||||
|
|
||||||
#include <grpc/support/log.h> |
|
||||||
|
|
||||||
void gpr_mpscq_init(gpr_mpscq* q) { |
|
||||||
gpr_atm_no_barrier_store(&q->head, (gpr_atm)&q->stub); |
|
||||||
q->tail = &q->stub; |
|
||||||
gpr_atm_no_barrier_store(&q->stub.next, (gpr_atm)NULL); |
|
||||||
} |
|
||||||
|
|
||||||
void gpr_mpscq_destroy(gpr_mpscq* q) { |
|
||||||
GPR_ASSERT(gpr_atm_no_barrier_load(&q->head) == (gpr_atm)&q->stub); |
|
||||||
GPR_ASSERT(q->tail == &q->stub); |
|
||||||
} |
|
||||||
|
|
||||||
bool gpr_mpscq_push(gpr_mpscq* q, gpr_mpscq_node* n) { |
|
||||||
gpr_atm_no_barrier_store(&n->next, (gpr_atm)NULL); |
|
||||||
gpr_mpscq_node* prev = |
|
||||||
(gpr_mpscq_node*)gpr_atm_full_xchg(&q->head, (gpr_atm)n); |
|
||||||
gpr_atm_rel_store(&prev->next, (gpr_atm)n); |
|
||||||
return prev == &q->stub; |
|
||||||
} |
|
||||||
|
|
||||||
gpr_mpscq_node* gpr_mpscq_pop(gpr_mpscq* q) { |
|
||||||
bool empty; |
|
||||||
return gpr_mpscq_pop_and_check_end(q, &empty); |
|
||||||
} |
|
||||||
|
|
||||||
gpr_mpscq_node* gpr_mpscq_pop_and_check_end(gpr_mpscq* q, bool* empty) { |
|
||||||
gpr_mpscq_node* tail = q->tail; |
|
||||||
gpr_mpscq_node* next = (gpr_mpscq_node*)gpr_atm_acq_load(&tail->next); |
|
||||||
if (tail == &q->stub) { |
|
||||||
// indicates the list is actually (ephemerally) empty
|
|
||||||
if (next == nullptr) { |
|
||||||
*empty = true; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
q->tail = next; |
|
||||||
tail = next; |
|
||||||
next = (gpr_mpscq_node*)gpr_atm_acq_load(&tail->next); |
|
||||||
} |
|
||||||
if (next != nullptr) { |
|
||||||
*empty = false; |
|
||||||
q->tail = next; |
|
||||||
return tail; |
|
||||||
} |
|
||||||
gpr_mpscq_node* head = (gpr_mpscq_node*)gpr_atm_acq_load(&q->head); |
|
||||||
if (tail != head) { |
|
||||||
*empty = false; |
|
||||||
// indicates a retry is in order: we're still adding
|
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
gpr_mpscq_push(q, &q->stub); |
|
||||||
next = (gpr_mpscq_node*)gpr_atm_acq_load(&tail->next); |
|
||||||
if (next != nullptr) { |
|
||||||
*empty = false; |
|
||||||
q->tail = next; |
|
||||||
return tail; |
|
||||||
} |
|
||||||
// indicates a retry is in order: we're still adding
|
|
||||||
*empty = false; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
void gpr_locked_mpscq_init(gpr_locked_mpscq* q) { |
|
||||||
gpr_mpscq_init(&q->queue); |
|
||||||
gpr_mu_init(&q->mu); |
|
||||||
} |
|
||||||
|
|
||||||
void gpr_locked_mpscq_destroy(gpr_locked_mpscq* q) { |
|
||||||
gpr_mpscq_destroy(&q->queue); |
|
||||||
gpr_mu_destroy(&q->mu); |
|
||||||
} |
|
||||||
|
|
||||||
bool gpr_locked_mpscq_push(gpr_locked_mpscq* q, gpr_mpscq_node* n) { |
|
||||||
return gpr_mpscq_push(&q->queue, n); |
|
||||||
} |
|
||||||
|
|
||||||
gpr_mpscq_node* gpr_locked_mpscq_try_pop(gpr_locked_mpscq* q) { |
|
||||||
if (gpr_mu_trylock(&q->mu)) { |
|
||||||
gpr_mpscq_node* n = gpr_mpscq_pop(&q->queue); |
|
||||||
gpr_mu_unlock(&q->mu); |
|
||||||
return n; |
|
||||||
} |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
gpr_mpscq_node* gpr_locked_mpscq_pop(gpr_locked_mpscq* q) { |
|
||||||
gpr_mu_lock(&q->mu); |
|
||||||
bool empty = false; |
|
||||||
gpr_mpscq_node* n; |
|
||||||
do { |
|
||||||
n = gpr_mpscq_pop_and_check_end(&q->queue, &empty); |
|
||||||
} while (n == nullptr && !empty); |
|
||||||
gpr_mu_unlock(&q->mu); |
|
||||||
return n; |
|
||||||
} |
|
@ -1,88 +0,0 @@ |
|||||||
/*
|
|
||||||
* |
|
||||||
* Copyright 2016 gRPC authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
* |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef GRPC_CORE_LIB_GPR_MPSCQ_H |
|
||||||
#define GRPC_CORE_LIB_GPR_MPSCQ_H |
|
||||||
|
|
||||||
#include <grpc/support/port_platform.h> |
|
||||||
|
|
||||||
#include <grpc/support/atm.h> |
|
||||||
#include <grpc/support/sync.h> |
|
||||||
#include <stdbool.h> |
|
||||||
#include <stddef.h> |
|
||||||
|
|
||||||
// Multiple-producer single-consumer lock free queue, based upon the
|
|
||||||
// implementation from Dmitry Vyukov here:
|
|
||||||
// http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
|
|
||||||
|
|
||||||
// List node (include this in a data structure at the top, and add application
|
|
||||||
// fields after it - to simulate inheritance)
|
|
||||||
typedef struct gpr_mpscq_node { |
|
||||||
gpr_atm next; |
|
||||||
} gpr_mpscq_node; |
|
||||||
|
|
||||||
// Actual queue type
|
|
||||||
typedef struct gpr_mpscq { |
|
||||||
// make sure head & tail don't share a cacheline
|
|
||||||
union { |
|
||||||
char padding[GPR_CACHELINE_SIZE]; |
|
||||||
gpr_atm head; |
|
||||||
}; |
|
||||||
gpr_mpscq_node* tail; |
|
||||||
gpr_mpscq_node stub; |
|
||||||
} gpr_mpscq; |
|
||||||
|
|
||||||
void gpr_mpscq_init(gpr_mpscq* q); |
|
||||||
void gpr_mpscq_destroy(gpr_mpscq* q); |
|
||||||
// Push a node
|
|
||||||
// Thread safe - can be called from multiple threads concurrently
|
|
||||||
// Returns true if this was possibly the first node (may return true
|
|
||||||
// sporadically, will not return false sporadically)
|
|
||||||
bool gpr_mpscq_push(gpr_mpscq* q, gpr_mpscq_node* n); |
|
||||||
// Pop a node (returns NULL if no node is ready - which doesn't indicate that
|
|
||||||
// the queue is empty!!)
|
|
||||||
// Thread compatible - can only be called from one thread at a time
|
|
||||||
gpr_mpscq_node* gpr_mpscq_pop(gpr_mpscq* q); |
|
||||||
// Pop a node; sets *empty to true if the queue is empty, or false if it is not
|
|
||||||
gpr_mpscq_node* gpr_mpscq_pop_and_check_end(gpr_mpscq* q, bool* empty); |
|
||||||
|
|
||||||
// An mpscq with a lock: it's safe to pop from multiple threads, but doing
|
|
||||||
// only one thread will succeed concurrently
|
|
||||||
typedef struct gpr_locked_mpscq { |
|
||||||
gpr_mpscq queue; |
|
||||||
gpr_mu mu; |
|
||||||
} gpr_locked_mpscq; |
|
||||||
|
|
||||||
void gpr_locked_mpscq_init(gpr_locked_mpscq* q); |
|
||||||
void gpr_locked_mpscq_destroy(gpr_locked_mpscq* q); |
|
||||||
// Push a node
|
|
||||||
// Thread safe - can be called from multiple threads concurrently
|
|
||||||
// Returns true if this was possibly the first node (may return true
|
|
||||||
// sporadically, will not return false sporadically)
|
|
||||||
bool gpr_locked_mpscq_push(gpr_locked_mpscq* q, gpr_mpscq_node* n); |
|
||||||
|
|
||||||
// Pop a node (returns NULL if no node is ready - which doesn't indicate that
|
|
||||||
// the queue is empty!!)
|
|
||||||
// Thread safe - can be called from multiple threads concurrently
|
|
||||||
gpr_mpscq_node* gpr_locked_mpscq_try_pop(gpr_locked_mpscq* q); |
|
||||||
|
|
||||||
// Pop a node. Returns NULL only if the queue was empty at some point after
|
|
||||||
// calling this function
|
|
||||||
gpr_mpscq_node* gpr_locked_mpscq_pop(gpr_locked_mpscq* q); |
|
||||||
|
|
||||||
#endif /* GRPC_CORE_LIB_GPR_MPSCQ_H */ |
|
@ -0,0 +1,108 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2016 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/lib/gprpp/mpscq.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
//
|
||||||
|
// MultiProducerSingleConsumerQueue
|
||||||
|
//
|
||||||
|
|
||||||
|
bool MultiProducerSingleConsumerQueue::Push(Node* node) { |
||||||
|
node->next.Store(nullptr, MemoryOrder::RELAXED); |
||||||
|
Node* prev = head_.Exchange(node, MemoryOrder::ACQ_REL); |
||||||
|
prev->next.Store(node, MemoryOrder::RELEASE); |
||||||
|
return prev == &stub_; |
||||||
|
} |
||||||
|
|
||||||
|
MultiProducerSingleConsumerQueue::Node* |
||||||
|
MultiProducerSingleConsumerQueue::Pop() { |
||||||
|
bool empty; |
||||||
|
return PopAndCheckEnd(&empty); |
||||||
|
} |
||||||
|
|
||||||
|
MultiProducerSingleConsumerQueue::Node* |
||||||
|
MultiProducerSingleConsumerQueue::PopAndCheckEnd(bool* empty) { |
||||||
|
Node* tail = tail_; |
||||||
|
Node* next = tail_->next.Load(MemoryOrder::ACQUIRE); |
||||||
|
if (tail == &stub_) { |
||||||
|
// indicates the list is actually (ephemerally) empty
|
||||||
|
if (next == nullptr) { |
||||||
|
*empty = true; |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
tail_ = next; |
||||||
|
tail = next; |
||||||
|
next = tail->next.Load(MemoryOrder::ACQUIRE); |
||||||
|
} |
||||||
|
if (next != nullptr) { |
||||||
|
*empty = false; |
||||||
|
tail_ = next; |
||||||
|
return tail; |
||||||
|
} |
||||||
|
Node* head = head_.Load(MemoryOrder::ACQUIRE); |
||||||
|
if (tail != head) { |
||||||
|
*empty = false; |
||||||
|
// indicates a retry is in order: we're still adding
|
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
Push(&stub_); |
||||||
|
next = tail->next.Load(MemoryOrder::ACQUIRE); |
||||||
|
if (next != nullptr) { |
||||||
|
*empty = false; |
||||||
|
tail_ = next; |
||||||
|
return tail; |
||||||
|
} |
||||||
|
// indicates a retry is in order: we're still adding
|
||||||
|
*empty = false; |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// LockedMultiProducerSingleConsumerQueue
|
||||||
|
//
|
||||||
|
|
||||||
|
bool LockedMultiProducerSingleConsumerQueue::Push(Node* node) { |
||||||
|
return queue_.Push(node); |
||||||
|
} |
||||||
|
|
||||||
|
LockedMultiProducerSingleConsumerQueue::Node* |
||||||
|
LockedMultiProducerSingleConsumerQueue::TryPop() { |
||||||
|
if (gpr_mu_trylock(mu_.get())) { |
||||||
|
Node* node = queue_.Pop(); |
||||||
|
gpr_mu_unlock(mu_.get()); |
||||||
|
return node; |
||||||
|
} |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
LockedMultiProducerSingleConsumerQueue::Node* |
||||||
|
LockedMultiProducerSingleConsumerQueue::Pop() { |
||||||
|
MutexLock lock(&mu_); |
||||||
|
bool empty = false; |
||||||
|
Node* node; |
||||||
|
do { |
||||||
|
node = queue_.PopAndCheckEnd(&empty); |
||||||
|
} while (node == nullptr && !empty); |
||||||
|
return node; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace grpc_core
|
@ -0,0 +1,98 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2016 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef GRPC_CORE_LIB_GPRPP_MPSCQ_H |
||||||
|
#define GRPC_CORE_LIB_GPRPP_MPSCQ_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/lib/gprpp/atomic.h" |
||||||
|
#include "src/core/lib/gprpp/sync.h" |
||||||
|
|
||||||
|
#include <grpc/support/log.h> |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
// Multiple-producer single-consumer lock free queue, based upon the
|
||||||
|
// implementation from Dmitry Vyukov here:
|
||||||
|
// http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
|
||||||
|
class MultiProducerSingleConsumerQueue { |
||||||
|
public: |
||||||
|
// List node. Application node types can inherit from this.
|
||||||
|
struct Node { |
||||||
|
Atomic<Node*> next; |
||||||
|
}; |
||||||
|
|
||||||
|
MultiProducerSingleConsumerQueue() : head_{&stub_}, tail_(&stub_) {} |
||||||
|
~MultiProducerSingleConsumerQueue() { |
||||||
|
GPR_ASSERT(head_.Load(MemoryOrder::RELAXED) == &stub_); |
||||||
|
GPR_ASSERT(tail_ == &stub_); |
||||||
|
} |
||||||
|
|
||||||
|
// Push a node
|
||||||
|
// Thread safe - can be called from multiple threads concurrently
|
||||||
|
// Returns true if this was possibly the first node (may return true
|
||||||
|
// sporadically, will not return false sporadically)
|
||||||
|
bool Push(Node* node); |
||||||
|
// Pop a node (returns NULL if no node is ready - which doesn't indicate that
|
||||||
|
// the queue is empty!!)
|
||||||
|
// Thread compatible - can only be called from one thread at a time
|
||||||
|
Node* Pop(); |
||||||
|
// Pop a node; sets *empty to true if the queue is empty, or false if it is
|
||||||
|
// not.
|
||||||
|
Node* PopAndCheckEnd(bool* empty); |
||||||
|
|
||||||
|
private: |
||||||
|
// make sure head & tail don't share a cacheline
|
||||||
|
union { |
||||||
|
char padding_[GPR_CACHELINE_SIZE]; |
||||||
|
Atomic<Node*> head_; |
||||||
|
}; |
||||||
|
Node* tail_; |
||||||
|
Node stub_; |
||||||
|
}; |
||||||
|
|
||||||
|
// An mpscq with a lock: it's safe to pop from multiple threads, but doing
|
||||||
|
// only one thread will succeed concurrently.
|
||||||
|
class LockedMultiProducerSingleConsumerQueue { |
||||||
|
public: |
||||||
|
typedef MultiProducerSingleConsumerQueue::Node Node; |
||||||
|
|
||||||
|
// Push a node
|
||||||
|
// Thread safe - can be called from multiple threads concurrently
|
||||||
|
// Returns true if this was possibly the first node (may return true
|
||||||
|
// sporadically, will not return false sporadically)
|
||||||
|
bool Push(Node* node); |
||||||
|
|
||||||
|
// Pop a node (returns NULL if no node is ready - which doesn't indicate that
|
||||||
|
// the queue is empty!!)
|
||||||
|
// Thread safe - can be called from multiple threads concurrently
|
||||||
|
Node* TryPop(); |
||||||
|
|
||||||
|
// Pop a node. Returns NULL only if the queue was empty at some point after
|
||||||
|
// calling this function
|
||||||
|
Node* Pop(); |
||||||
|
|
||||||
|
private: |
||||||
|
MultiProducerSingleConsumerQueue queue_; |
||||||
|
Mutex mu_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif /* GRPC_CORE_LIB_GPRPP_MPSCQ_H */ |
Loading…
Reference in new issue