文章目录

  • 一、MessageQueue 的 Java 层机制
  • 二、MessageQueue 的 native 层阻塞机制
  • 三、MessageQueue 的 native 层解除阻塞机制
  • 三、MessageQueue 的 native 层 JNI 方法动态注册
  • 三、MessageQueue 的 native 层完整代码 android_os_MessageQueue.cpp

一、MessageQueue 的 Java 层机制


之前在 【Android 异步操作】手写 Handler ( 消息队列 MessageQueue | 消息保存到链表 | 从链表中获取消息 ) 中 , 模仿 Android 的 MessageQueue 手写的 MessageQueue , 使用了如下同步机制 ,

从 消息队列 MessageQueue 中取出 消息 Message ,

如果当前链表为空 , 此时会 调用 wait 方法阻塞 , 直到消息入队时 , 链表中有了元素 , 会调用 notify 解除该阻塞 ;

在实际的 Android 中的 消息队列 MessageQueue 的同步机制 是在 native 层实现 的 ;

在创建 消息队列 MessageQueue 时 , 调用了 nativeInit() 方法 , 销毁 MessageQueue 时调用 nativeDestroy 方法 ;

如果调用 next 获取下一个消息时 , 如果当前消息队列 MessageQueue 中没有消息 , 此时需要阻塞 , 调用 nativePollOnce 即可实现在 native 阻塞线程 ;

    // 初始化 MessageQueue 时调用的方法 private native static long nativeInit();// 销毁 MessageQueue 时调用的方法 private native static void nativeDestroy(long ptr);// 线程阻塞方法@UnsupportedAppUsageprivate native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/// 线程唤醒方法 private native static void nativeWake(long ptr);private native static boolean nativeIsPolling(long ptr);private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);// 此处初始化 MessageQueue , 调用了 nativeInit 方法 MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}// 获取消息队列中的下一个消息 @UnsupportedAppUsageMessage next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// 此处阻塞线程 nativePollOnce(ptr, nextPollTimeoutMillis);}}

二、MessageQueue 的 native 层阻塞机制


线程阻塞方法 private native void nativePollOnce(long ptr, int timeoutMillis) , 是 native 方法 , 该方法在 frameworks/base/core/jni/android_os_MessageQueue.cpp 中实现 ,

从 Java 层传入 long 类型 , 然后转为 NativeMessageQueue* 类型指针 ,

该 Java 层传入的 long 类型是初始化消息队列时 , 由 nativeInit 方法返回 , 是 消息队列在 Native 层的指针 ,

之后 NativeMessageQueue 指针调用了其本身的 pollOnce 函数 , 该函数中 , 主要调用了 Looper 的 pollOnce 函数 , mLooper->pollOnce(timeoutMillis) ;

frameworks/base/core/jni/android_os_MessageQueue.cpp 中阻塞相关源码 :

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {mPollEnv = env;mPollObj = pollObj;// 这是主要操作 mLooper->pollOnce(timeoutMillis);mPollObj = NULL;mPollEnv = NULL;if (mExceptionObj) {env->Throw(mExceptionObj);env->DeleteLocalRef(mExceptionObj);mExceptionObj = NULL;}
}static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

参考 : frameworks/base/core/jni/android_os_MessageQueue.cpp

继续查看 Native 层 Looper.cpp 的 pollOnce 方法 ,

Looper.cpp 源码路径是 system/core/libutils/Looper.cpp ,

在该方法中 , 最终调用 Looper.cpp 的 pollInner 方法 ,

在 pollInner 方法中 , 调用了 epoll_wait 方法 , 该方法就是等待方法 , 在该方法中会监听 mEpollFd 文件句柄 ,

       #include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);int epoll_pwait(int epfd, struct epoll_event *events,int maxevents, int timeout,const sigset_t *sigmask);

参考 : epoll_wait

system/core/libutils/Looper.cpp 中阻塞相关源码 :

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0;for (;;) {while (mResponseIndex < mResponses.size()) {const Response& response = mResponses.itemAt(mResponseIndex++);int ident = response.request.ident;if (ident >= 0) {int fd = response.request.fd;int events = response.events;void* data = response.request.data;
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - returning signalled identifier %d: ""fd=%d, events=0x%x, data=%p",this, ident, fd, events, data);
#endifif (outFd != NULL) *outFd = fd;if (outEvents != NULL) *outEvents = events;if (outData != NULL) *outData = data;return ident;}}if (result != 0) {#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - returning result %d", this, result);
#endifif (outFd != NULL) *outFd = 0;if (outEvents != NULL) *outEvents = 0;if (outData != NULL) *outData = NULL;return result;}result = pollInner(timeoutMillis);}
}int Looper::pollInner(int timeoutMillis) {// ... int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}

参考 : system/core/libutils/Looper.cpp

三、MessageQueue 的 native 层解除阻塞机制


在 MessageQueue 消息队列的 Java 层 , 将 Message 消息插入到链表表头后 , 调用了 nativeWake 方法 , 唤醒了线程 , 即解除了阻塞 ;

public final class MessageQueue {boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}
}

在 native 层的 frameworks/base/core/jni/android_os_MessageQueue.cpp 实现了上述

Java 层定义的 private native static void nativeWake(long ptr) 方法 ,

注册 JNI 方法方式是动态注册 , 注册的参数如下 , Java 层的 nativeWake 对应的 native 层方法是 android_os_MessageQueue_nativeWake 方法 ,

static const JNINativeMethod gMessageQueueMethods[] = {/* name, signature, funcPtr */{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },{ "nativeSetFileDescriptorEvents", "(JII)V",(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};

参考 : frameworks/base/core/jni/android_os_MessageQueue.cpp

下面是 frameworks/base/core/jni/android_os_MessageQueue.cpp 中的相关方法实现 ,

在 android_os_MessageQueue_nativeWake 方法中调用了 本身的 wake 方法 ,

在 wake 方法中调用了 system/core/libutils/Looper.cpp 中的 wake 方法 ;

void NativeMessageQueue::wake() {// 此处调用了 Looper 的 wake 函数 mLooper->wake();
}static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->wake();
}

参考 : frameworks/base/core/jni/android_os_MessageQueue.cpp

查看 system/core/libutils/Looper.cpp 中的 wake 方法 , 在该方法中 ,

ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t))) 代码说明 ,

向 mWakeEventFd 文件句柄写入了数据 ;

void Looper::wake() {#if DEBUG_POLL_AND_WAKEALOGD("%p ~ wake", this);
#endifuint64_t inc = 1;ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));if (nWrite != sizeof(uint64_t)) {if (errno != EAGAIN) {LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",mWakeEventFd, strerror(errno));}}
}

参考 : system/core/libutils/Looper.cpp

阻塞的时候使用的是 mEpollFd 文件句柄 ,

唤醒的时候使用的是 mWakeEventFd 文件句柄 ,

下面分析这两个文件句柄之间的联系 ;

Looper 构造函数 , 调用了 rebuildEpollLocked() 方法 ,

在 rebuildEpollLocked 方法 中调用 mEpollFd = epoll_create(EPOLL_SIZE_HINT) , 创建了一个句柄 ,

调用 int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem) 注册事件监听 ,

注册 mEpollFd 句柄 , 监听 mWakeEventFd 句柄的 eventItem 事件 ,

监听的事件是 eventItem.events = EPOLLIN 事件 ,

该事件代表 , 向 mWakeEventFd 文件句柄写入数据 , 此时就对应解除 epoll_wait 阻塞 ;

system/core/libutils/Looper.cpp 中 Looper 构造函数 , rebuildEpollLocked 方法 :

Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",strerror(errno));AutoMutex _l(mLock);rebuildEpollLocked();
}void Looper::rebuildEpollLocked() {// Close old epoll instance if we have one.if (mEpollFd >= 0) {#if DEBUG_CALLBACKSALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endifclose(mEpollFd);}// Allocate the new epoll instance and register the wake pipe.// 创建句柄 mEpollFd = epoll_create(EPOLL_SIZE_HINT);LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));struct epoll_event eventItem;memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union// 注册监听的事件eventItem.events = EPOLLIN;eventItem.data.fd = mWakeEventFd;// 注册事件监听 int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",strerror(errno));for (size_t i = 0; i < mRequests.size(); i++) {const Request& request = mRequests.valueAt(i);struct epoll_event eventItem;request.initEventItem(&eventItem);int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);if (epollResult < 0) {ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",request.fd, strerror(errno));}}
}

MessageQueue 消息队列是通过 Linux 的 epoll 机制实现的阻塞 ;

三、MessageQueue 的 native 层 JNI 方法动态注册


JNI 动态注册 , 消息队列 MessageQueue 中的注册方法 , 使用的是动态注册 ,

static const JNINativeMethod gMessageQueueMethods[] = {/* name, signature, funcPtr */{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },{ "nativeSetFileDescriptorEvents", "(JII)V",(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};// 动态注册 JNI 函数
int register_android_os_MessageQueue(JNIEnv* env) {int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods,NELEM(gMessageQueueMethods));jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,"dispatchEvents", "(II)I");return res;
}

详情参考 : 【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives )

三、MessageQueue 的 native 层完整代码 android_os_MessageQueue.cpp


完整的 android_os_MessageQueue.cpp 代码 :

/** Copyright (C) 2010 The Android Open Source Project** 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.*/#define LOG_TAG "MessageQueue-JNI"#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>#include <utils/Looper.h>
#include <utils/Log.h>
#include "android_os_MessageQueue.h"#include "core_jni_helpers.h"namespace android {static struct {jfieldID mPtr;   // native object attached to the DVM MessageQueuejmethodID dispatchEvents;
} gMessageQueueClassInfo;// Must be kept in sync with the constants in Looper.FileDescriptorCallback
static const int CALLBACK_EVENT_INPUT = 1 << 0;
static const int CALLBACK_EVENT_OUTPUT = 1 << 1;
static const int CALLBACK_EVENT_ERROR = 1 << 2;class NativeMessageQueue : public MessageQueue, public LooperCallback {public:NativeMessageQueue();virtual ~NativeMessageQueue();virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis);void wake();void setFileDescriptorEvents(int fd, int events);virtual int handleEvent(int fd, int events, void* data);private:JNIEnv* mPollEnv;jobject mPollObj;jthrowable mExceptionObj;
};MessageQueue::MessageQueue() {}MessageQueue::~MessageQueue() {}bool MessageQueue::raiseAndClearException(JNIEnv* env, const char* msg) {if (env->ExceptionCheck()) {jthrowable exceptionObj = env->ExceptionOccurred();env->ExceptionClear();raiseException(env, msg, exceptionObj);env->DeleteLocalRef(exceptionObj);return true;}return false;
}NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {mLooper = Looper::getForThread();if (mLooper == NULL) {mLooper = new Looper(false);Looper::setForThread(mLooper);}
}NativeMessageQueue::~NativeMessageQueue() {}void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) {if (exceptionObj) {if (mPollEnv == env) {if (mExceptionObj) {env->DeleteLocalRef(mExceptionObj);}mExceptionObj = jthrowable(env->NewLocalRef(exceptionObj));ALOGE("Exception in MessageQueue callback: %s", msg);jniLogException(env, ANDROID_LOG_ERROR, LOG_TAG, exceptionObj);} else {ALOGE("Exception: %s", msg);jniLogException(env, ANDROID_LOG_ERROR, LOG_TAG, exceptionObj);LOG_ALWAYS_FATAL("raiseException() was called when not in a callback, exiting.");}}
}void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {mPollEnv = env;mPollObj = pollObj;mLooper->pollOnce(timeoutMillis);mPollObj = NULL;mPollEnv = NULL;if (mExceptionObj) {env->Throw(mExceptionObj);env->DeleteLocalRef(mExceptionObj);mExceptionObj = NULL;}
}void NativeMessageQueue::wake() {mLooper->wake();
}void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) {if (events) {int looperEvents = 0;if (events & CALLBACK_EVENT_INPUT) {looperEvents |= Looper::EVENT_INPUT;}if (events & CALLBACK_EVENT_OUTPUT) {looperEvents |= Looper::EVENT_OUTPUT;}mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,reinterpret_cast<void*>(events));} else {mLooper->removeFd(fd);}
}int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {int events = 0;if (looperEvents & Looper::EVENT_INPUT) {events |= CALLBACK_EVENT_INPUT;}if (looperEvents & Looper::EVENT_OUTPUT) {events |= CALLBACK_EVENT_OUTPUT;}if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) {events |= CALLBACK_EVENT_ERROR;}int oldWatchedEvents = reinterpret_cast<intptr_t>(data);int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj,gMessageQueueClassInfo.dispatchEvents, fd, events);if (!newWatchedEvents) {return 0; // unregister the fd}if (newWatchedEvents != oldWatchedEvents) {setFileDescriptorEvents(fd, newWatchedEvents);}return 1;
}// ----------------------------------------------------------------------------sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {jlong ptr = env->GetLongField(messageQueueObj, gMessageQueueClassInfo.mPtr);return reinterpret_cast<NativeMessageQueue*>(ptr);
}static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();if (!nativeMessageQueue) {jniThrowRuntimeException(env, "Unable to allocate native queue");return 0;}nativeMessageQueue->incStrong(env);return reinterpret_cast<jlong>(nativeMessageQueue);
}static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->decStrong(env);
}static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->wake();
}static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass clazz, jlong ptr) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);return nativeMessageQueue->getLooper()->isPolling();
}static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz,jlong ptr, jint fd, jint events) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->setFileDescriptorEvents(fd, events);
}// ----------------------------------------------------------------------------// 动态注册 JNI 函数的结构体
// 每个结构体中的元素是 Java 方法名称 , 方法签名 , C++ 中的方法指针
static const JNINativeMethod gMessageQueueMethods[] = {/* name, signature, funcPtr */{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },{ "nativeSetFileDescriptorEvents", "(JII)V",(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};// 动态注册 JNI 函数
int register_android_os_MessageQueue(JNIEnv* env) {int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods,NELEM(gMessageQueueMethods));jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,"dispatchEvents", "(II)I");return res;
}} // namespace android

参考 : frameworks/base/core/jni/android_os_MessageQueue.cpp

【Android 异步操作】Handler 机制 ( MessageQueue 消息队列的阻塞机制 | Java 层机制 | native 层阻塞机制 | native 层解除阻塞机制 )相关推荐

  1. 【Android 异步操作】Handler 机制 ( Android 提供的 Handler 源码解析 | Handler 构造与消息分发 | MessageQueue 消息队列相关方法 )

    文章目录 一.Handler 构造函数 二.Handler 消息分发 三.MessageQueue 消息队列相关函数 一.Handler 构造函数 一般使用 Handler 时 , 调用 Handle ...

  2. Android开发 ---多线程操作:Handler对象,消息队列,异步任务下载

    效果图: 1.activity_main.xml 描述:定义了六个按钮 <?xml version="1.0" encoding="utf-8"?> ...

  3. Linux C语言在用户态实现一个低时延通知(eventfd)+轮询(无锁队列ring)机制的消息队列

    目录 fastq.c fastq.h test-0.c test-1.c https://github.com/Rtoax/test/tree/master/ipc/github/fastq fast ...

  4. ROS 搞懂多话题回调机制以及消息队列

    ROS 消息队列的运行机制 下面只是自己的理解,如果有误望大家指正! 大部分内容参考于 https://blog.csdn.net/qq_42700518/article/details/104453 ...

  5. MQ(MessageQueue)消息队列

    同步调用 同步调用的优点: 时效性较强,可以立即得到结果 同步调用的问题: 耦合度高: 每次加入新的需求,都要修改原来的代码 性能和吞吐能力下降: 调用者需要等待服务提供者响应,如果调用链过长则响应时 ...

  6. php消息队列重发机制,使用消息队列的注意事项

    前面有介绍过消息队列的好处,通过理解这些好处,我们就能找到其应用场景. 事情大多都有两面性,而消息队列,当我们享受它的便捷时,也需要考虑到一些注意事项. > 这里,举一些常见的注意事项,使用过程 ...

  7. RTT的IPC机制篇——消息队列

    RTT野火18章消息队列 2018年12月29日 10:47 相当于裸机中的数组. 一个消息框保存一条消息. 消息框的总数即消息队列的长度. 消息框的大小就是消息的大小. 消息队列中第一个消息框称为: ...

  8. 消息队列RabbitMQ基本使用(Java代码实现)

    同步通讯的问题 调用链中每个服务在等待响应的过程中,不能释放请求占用的资源,如果服务级联失败,提供者出现故障,会导致所有调用方出现问题. 这里介绍几个概念,以购买商品为例,支付服务是事件发布者(pub ...

  9. java消息队列mq_我爱java系列---【消息队列(rabbitmq)】

    使用消息队列来避免分布式事务 如果仔细观察生活的话,生活的很多场景已经给了我们提示. 比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,往往是给你一张小票,然后让你拿着小票 ...

最新文章

  1. 三大深度学习生成模型:VAE、GAN及其变种
  2. GraphQL学习过程应该是这样的
  3. python教程是什么-Python基础教程_Python入门知识
  4. python简单超级马里奥游戏下载_python 实现超级玛丽游戏
  5. wxWidgets:wxWidgets 辅助功能示例
  6. ArcGIS中生成蜂窝多边形算法解析
  7. table 内 下拉列表 被遮挡_一个简洁、有趣的无限下拉方案
  8. html5图片长按保存,一文彻底解决HTML5页面中长按保存图片功能
  9. 领域应用 | 推荐算法不够精准?让知识图谱来解决
  10. 服务端接口中的那些坑
  11. 通过函数指针实现四则运算
  12. 学习Spring,这篇就够了
  13. 硬盘根目录里的Msdia80.dll文件
  14. html 鼠标放上去变色,css3 鼠标经过div背景变色
  15. 学习笔记2018-10-26 读论文A single algorithm to retrieve turbidity from remotely-sensed data in all coastal
  16. 尾部关性尾部风险平价和圣杯分布
  17. 《基础会计学》期末模拟试题及答案
  18. Latex 中文简历 过程(更新Miktex和 修改utf字体)
  19. (1)3DMAX之界面认识
  20. 通达信V6.1概念板块分类文件格式分析

热门文章

  1. 机器学习中的算法(1)-决策树模型组合之随机森林与GBDT
  2. 如何授予邮箱的代理发送权限
  3. 企业微信提示服务商未响应请求,将无法获取用户事件回调
  4. Bootstrap UI 编辑器
  5. Vector Clock理解
  6. hdu 5037 周期优化
  7. Global Mapper总汇
  8. linux kernel 2.6.36 编译升级
  9. 课后练习----实现窗口的切换
  10. Excel的日期格式约定与解析