首页 > 其他分享 >Muduo库之同步原语

Muduo库之同步原语

时间:2022-10-14 16:57:52浏览次数:91  
标签:__ Muduo void mu 原语 同步 线程 type ptr

Atomic

Atomic.h 文件中定义了原子操作类类型 AtomicIntegerT<T>,它使用了 GCC 内置的原子操作来实现。

原子操作在多线程开发中经常用到,比如计数器、序列产生器等。这些情况下数据有并发的危险,但是用锁去保护又显得有些浪费,会造成一定的性能瓶颈,而原子操作则更为节省资源,所以原子操作最为合适。

比如,常见的 i++ 操作便不是原子操作,它通常分为以下三步:

  1. 从内存将变量的数值取到寄存器中;
  2. 对寄存器中的数值加1;
  3. 将寄存器中的数值存入到内存中;

由于时序的问题,所以如果多个线程同时操作同一个全局变量,就会出现问题。

这里所说的原子操作,基本包含如下语义:

  1. 操作本身是不可分割的;
  2. 一个线程对某个数据的操作何时对另外一个线程可见;
  3. 执行的顺序是否可以被重排;

GCC 自 4.1.2 版本之后,对 X86 或 X86_64 支持内置原子操作。也就是说,无需引入第三方库的锁保护,便可对 1、2、4、8 字节的数值或指针类型进行原子 加/减/与/或/异或 等操作。__sync_ 系列函数如下:

// 将 value 加到 ptr 上,结果更新到 ptr,并返回操作之前的 ptr 的值
type __sync_fetch_and_add(type* ptr, type value, ...);
// 从 ptr 减去 val,结果更新到 ptr,并返回操作之前的 ptr 的值
type __sync_fetch_and_sub(type* ptr, type value, ...);
// 将 ptr 与 value 相或,结果更新到 ptr,并返回操作之前的 ptr 的值
type __sync_fetch_and_or(type* ptr, type value, ...);
// 将 ptr 与 value 相与,结果更新到 ptr,并返回操作之前的 ptr 的值
type __sync_fetch_and_and(type* ptr, type value, ...);
// 将 ptr 与 value 异或,结果更新到 ptr,并返回操作之前的 ptr 的值
type __sync_fetch_and_xor(type* ptr, type value, ...);
// 将 ptr 取反后,与 value 相与,结果更新到 ptr,并返回操作之前的 ptr 的值
type __sync_fetch_and_nand(type* ptr, type value, ...);
// 将 value 加到 ptr 上,结果更新到 ptr,并返回操作之后新的 ptr 的值
type __sync_add_and_fetch(type* ptr, type value, ...);
// 从 ptr 减去 val,结果更新到 ptr,并返回操作之后新的 ptr 的值
type __sync_sub_and_fetch(type* ptr, type value, ...);
//  将 ptr 与 value 相或,结果更新到 ptr,并返回操作之后新的 ptr 的值
type __sync_or_and_fetch(type* ptr, type value, ...);
// 将 ptr 与 value 相与,结果更新到 ptr,并返回操作之后新的 ptr 的值
type __sync_and_and_fetch(type* ptr, type value, ...);
// 将 ptr 与 value 异或,结果更新到 ptr,并返回操作之后新的 ptr 的值
type __sync_xor_and_fetch(type* ptr, type value, ...);
// 将 ptr 取反后,与 value 相与,结果更新到 ptr,并返回操作之后新的 ptr 的值
type __sync_nand_and_fetch(type* ptr, type value, ...);
// 比较 ptr 与 oldval 的值,如果两者相等,则将 newval 更新到 ptr 并返回 true
bool __sync_bool_compare_and_swap(type* ptr, type oldval, type newval ...);
// 比较 ptr 与 oldval 的值,如果两者相等,则将 newval 更新到 ptr 并返回操作之前 ptr 的值
type __sync_val_compare_and_swap(type* ptr, type oldval, type newval, ...);
// 屏障函数
__sync_synchronize(...);
// 将 value 写入 ptr,对 ptr 加锁,并返回操作之前 ptr 的值,即 try spinlock 语义
type __sync_lock_test_and_set(type *ptr, type value, ...);
// 将 0 写入到 ptr,并对 ptr 解锁,即 unlock spinlock 语义
void __sync_lock_release(type *ptr, ...)

后面的可扩展参数(...)用来指出哪些变量需要 Memory Barrier,由于目前 GCC 实现的是 Full Barrier,因此可以忽略该参数。

在 GCC 实现了 C++11 之后,上述的 __sync 系列函数便不再推荐使用了,而是基于 C++11 的新原子操作接口,使用 __atomic 作为前缀,对于普通的数学操作函数,其函数接口形式为:

type __atomic_OP_fetch(type* ptr, type val, int memorder);
type __atomic_fetch_OP (type *ptr, type val, int memorder);

其中,OP 表示数学操作符号,类似于上述的 __sync 系列函数。

除此之外,还根据新标准提供了一系列新的接口:

type __atomic_load_n (type *ptr, int memorder);
void __atomic_store_n (type *ptr, type val, int memorder);
type __atomic_exchange_n (type *ptr, type val, int memorder);
bool __atomic_compare_exchange_n (type *ptr, type *expected, type desired, bool weak, int success_memorder, int failure_memorder);
bool __atomic_test_and_set (void *ptr, int memorder);
void __atomic_clear (bool *ptr, int memorder);
void __atomic_thread_fence (int memorder);
bool __atomic_always_lock_free (size_t size, void *ptr);
bool __atomic_is_lock_free (size_t size, void *ptr);

在模板类 AtomicIntegerT 中,只声明了一个成员变量 value_,该变量使用 volatile 关键字进行修饰。

使用 volatile 关键字所修饰的变量表示随时可能会被某个未知的因素进行修改,即表示它是易变的,常用于多线程开发中。如果某个变量被该关键字修饰,那么每次去读取该值时是从对应的地址中去读取,而不会因为编译器发现程序中间没有对该变量进行写入操作而不去内存上读取,这就阻止了编译器对该变量的优化,同时也保证了对特殊地址的稳定访问。

Mutex

线程安全分析

如果使用的是 Clang 编译器,则可以开启线程安全分析(Thread safety annotations),它是 C++ 语言的扩展,警告代码中潜在的竞争条件。它是完全静态的,没有运行时的开销。例如,如果变量 foo 由锁变量 mu 保护,那么每当一段代码在 foo 没有被锁定的情况下读取或写入时,分析就会发出警告。

#include "mutex.h"

class BankAccount {
private:
  Mutex mu;
  int   balance GUARDED_BY(mu);

  void depositImpl(int amount) {
    balance += amount;       // 警告!不能在没有加锁的情况下写入 balance 变量
  }

  void withdrawImpl(int amount) REQUIRES(mu) {
    balance -= amount;       // 正确!调用者必须加锁
  }

public:
  void withdraw(int amount) {
    mu.Lock();
    withdrawImpl(amount);    // 正确!我们加了锁
  }                          // 警告!并未解锁

  void transferFrom(BankAccount& b, int amount) {
    mu.Lock();
    b.withdrawImpl(amount);  // 警告!调用时需要对 b.mu 进行加锁
    depositImpl(amount);     // 正确,该函数没有需求
    mu.Unlock();
  }

在上述例子中,GUARDED_BY 属性声明线程必须先给 mu 加锁才能读取或写入 balance 变量,从而确保递增和递减操作是原子的。同样,REQUIRES 声明调用线程必须在调用之前加锁。因为调用者被假定为已经加锁,所以在方法的主体内修改变量 balance 是安全的。

要开启线程安全分析,只需使用 -Wthread-safety 标志进行编译即可,例如:

clang -c -Wthread-safety example.cpp

除此之外,还包括:

  • -Wthread-safety-attributes: 线程安全属性的语义检查
  • -Wthread-safety-analysis: 核心分析
  • -Wthread-safety-precise: 要求互斥体表达式精确匹配,对于具有大量别名的代码,可以禁用此警告
  • -Wthread-safety-reference: 检查受保护成员何时通过引用传递

GUARDED_BY

GUARDED_BY 是数据成员的一个属性,它声明数据成员受给定能力的保护。对数据的读操作需要共享访问,而写操作需要独占访问。PT_GUARDED_BY 类似,但旨在用于指针和智能指针。数据成员本身没有限制,但它指向的数据受给定能力的保护。

Mutex mu;
int *p1             GUARDED_BY(mu);
int *p2             PT_GUARDED_BY(mu);
unique_ptr<int> p3  PT_GUARDED_BY(mu);

void test() {
  p1 = 0;             // Warning!

  *p2 = 42;           // Warning!
  p2 = new int;       // OK.

  *p3 = 42;           // Warning!
  p3.reset(new int);  // OK.
}

REQUIRES

REQUIRES 是函数或方法的一个属性,它声明调用线程必须具有对给定功能的独占访问权限。可以指定不止一种能力。功能必须在进入功能时保留,并且在退出时仍必须保留。REQUIRES_SHARED 类似,但只需要共享访问。

Mutex mu1, mu2;
int a GUARDED_BY(mu1);
int b GUARDED_BY(mu2);

void foo() REQUIRES(mu1, mu2) {
  a = 0;
  b = 0;
}

void test() {
  mu1.Lock();
  foo();         // Warning!  Requires mu2.
  mu1.Unlock();
}

之前称作 EXCLUSIVE_LOCKS_REQUIREDSHARED_LOCKS_REQUIRED

ACQUIRE、RELEASE

ACQUIREACQUIRE_SHARED 是函数或方法的属性,声明该函数获得了一项能力,但不释放它。给定的能力不得在进入时保留,而将在退出时保留。

RELEASEACQUIRE_SHAREDRELEASE_GENERIC 声明该函数释放给定的能力。该功能必须在进入时保留,退出时将不再保留。

Mutex mu;
MyClass myObject GUARDED_BY(mu);

void lockAndInit() ACQUIRE(mu) {
  mu.Lock();
  myObject.init();
}

void cleanupAndUnlock() RELEASE(mu) {
  myObject.cleanup();
}                          // Warning!  Need to unlock mu.

void test() {
  lockAndInit();
  myObject.doSomething();
  cleanupAndUnlock();
  myObject.doSomething();  // Warning, mu is not locked.
}

如果没有参数传递给 ACQUIRERELEASE,那么参数被假定为 this,并且分析不会检查函数的主体。此模式旨在将加锁细节隐藏在抽象接口后面的类使用。例如:

template <class T>
class CAPABILITY("mutex") Container {
private:
  Mutex mu;
  T* data;

public:
  // Hide mu from public interface.
  void Lock()   ACQUIRE() { mu.Lock(); }
  void Unlock() RELEASE() { mu.Unlock(); }

  T& getElem(int i) { return data[i]; }
};

void test() {
  Container<int> c;
  c.Lock();
  int i = c.getElem(0);
  c.Unlock();
}

之前称作 EXCLUSE_LOCK_FUNCTIONSHARED_LOCK_FUNCTIONUNLOCK_FUNCTION

EXCLUDES

EXCLUDES 是函数或方法的一个属性,它声明调用者不能拥有给定的能力。此关键字用于防止死锁,许多互斥锁实现是不可重入的,因为如何函数第二次获取互斥锁,就会发生死锁。

Mutex mu;
int a GUARDED_BY(mu);

void clear() EXCLUDES(mu) {
  mu.Lock();
  a = 0;
  mu.Unlock();
}

void reset() {
  mu.Lock();
  clear();     // Warning!  Caller cannot hold 'mu'.
  mu.Unlock();
}

REQUIRES 不同的是,EXCLUDES 是可选的。如果缺少属性,分析不会发出警告,这在某些情况下可能会导致误报。

之前称作 LOCKS_EXCLUDED

NO_THREAD_SAFETY_ANALYSIS

NO_THREAD_SAFETY_ANALYIS 是函数或方法的一个属性,它关闭该方法的线程安全检查。

class Counter {
  Mutex mu;
  int a GUARDED_BY(mu);

  void unsafeIncrement() NO_THREAD_SAFETY_ANALYSIS { a++; }
};

与其他属性不同,NO_THREAD_SAFETY_ANALYSIS 不是函数接口的一部分,因此应放在函数定义(.cc 文件或 .cpp 文件)而不是函数声明中(头文件)。

RETURN_CAPABILITY

RETURN_CAPABILITY 是函数或方法的属性,它声明函数返回对给定功能的引用。它用于注释返回互斥锁的 getter() 方法。

class MyClass {
private:
  Mutex mu;
  int a GUARDED_BY(mu);

public:
  Mutex* getMu() RETURN_CAPABILITY(mu) { return &mu; }

  // analysis knows that getMu() == mu
  void clear() REQUIRES(getMu()) { a = 0; }
};

之前称作:LOCK_RETURNED

ACQUIRED

ACQUIRED_BEFOREACQUIRED_AFTER 是成员声明的属性,特别是互斥锁或其他功能的声明。这些声明强制执行必须获取互斥锁的特定顺序,以防止死锁。

Mutex m1;
Mutex m2 ACQUIRED_AFTER(m1);

// Alternative declaration
// Mutex m2;
// Mutex m1 ACQUIRED_BEFORE(m2);

void foo() {
  m2.Lock();
  m1.Lock();  // Warning!  m2 must be acquired after m1.
  m1.Unlock();
  m2.Unlock();
}

CAPABILITY

CAPABILITY 是类的一个属性,它指定类的对象可以用作能力,string 参数指定错误消息中的能力类型,例如 “mutex”。

template <class T>
class CAPABILITY("mutex") Container {
private:
  Mutex mu;
  T* data;

public:
  // Hide mu from public interface.
  void Lock()   ACQUIRE() { mu.Lock(); }
  void Unlock() RELEASE() { mu.Unlock(); }

  T& getElem(int i) { return data[i]; }
};

void test() {
  Container<int> c;
  c.Lock();
  int i = c.getElem(0);
  c.Unlock();
}

SOCPED_CAPABILITY

SCOPED_CAPABILITY 是实现 RAII 样式锁定的类的属性,其中在构造函数中获取能力,并在析构函数中释放。此类类需要特殊处理,因为构造函数和析构函数通过不同的名称引用能力。

SCOPED_CAPABILITY 被视为在构造时隐式获取并在销毁时释放的能力。它们与构造函数的线程安全属性中命名的一组常规功能相关联。其他成员函数上的获取类型属性被视为应用于该组的关联功能,同时 RELEASE 意味着函数以任何模式释放所有关联功能。

以前称作 SCOPED_LOCKABLE

TRY_ACQUIRE

这些是试图获取给定能力的函数或方法的属性,并返回一个指示成功或失败的布尔值。第一个参数必须是 truefalse,以指示哪个返回值指示成功,其余参数的解释与 ACQUIRE 相同。

Mutex mu;
int a GUARDED_BY(mu);

void foo() {
  bool success = mu.TryLock();
  a = 0;         // Warning, mu is not locked.
  if (success) {
    a = 0;       // Ok.
    mu.Unlock();
  } else {
    a = 0;       // Warning, mu is not locked.
  }
}

以前称作 EXCLUSIVE_TRYLOCK_FUNCTIONSHARED_TRYLOCK_FUNCTION

ASSERT_CAPABILITY

这些是断言调用线程已经拥有给定能力的函数或方法的属性,例如通过执行运行时测试并在未用有能力时终止。此注释的存在导致分析假定在调用注释函数后保留该功能。

以前称作 ASSERT_EXCLUSIVE_LOCKASSERT_SHARED_LOCK

返回值的检查

源码如下:

#ifdef CHECK_PTHREAD_RETURN_VALUE

#ifdef NDEBUG
__BEGIN_DECLS
extern void __assert_perror_fail (int errnum,
                                  const char *file,
                                  unsigned int line,
                                  const char *function)
    noexcept __attribute__ ((__noreturn__));
__END_DECLS
#endif // !NDEBUG
#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret);         \
        if (__builtin_expect(errnum != 0, 0))    \
        __assert_perror_fail (errnum, __FILE__, __LINE__, __func__);})

#else  // !CHECK_PTHREAD_RETURN_VALUE
#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret);         \
                       assert(errnum == 0); (void) errnum;})

#endif // !CHECK_PTHREAD_RETURN_VALUE

其中,__attribute__ ((__noreturn__)) 表示告诉编译器该函数不会返回,用来抑制关于未到达代码路径的错误。比如 C 库函数中的 abort()exit()

extern void exit(int)   __attribute__ ((__noreturn__));
extern void abort(void) __attribute__ ((__noreturn__));

其中的 __typeof__ 相当于 typeof(),而 __typeof__(ret) errnum 就是将 errnum 的数据类型定义为和 ret 相同的数据类型。

其中的 __BEGIN_DECLS__END_DECLS 是一组宏,其定义如下:

#if defined(__cplusplus)
    #define __BEGIN_DECLS   extern "C" {
    #define __END_DECLS     }
#else
    #define __BEGIN_DECLS
    #define __END_DECLS

也就是说,这两宏之间的部分相当于声明了 extern "C",即将该部分代码作为 C 语言代码进行使用。

MutexLock

MutexLock 用于封装临界区,使用 RAII 手法封装互斥器的创建与销毁。

MutexLock 的定义中,有两个成员变量,分别是 mutex_holder_,前者为要操作的互斥锁,后者代表当前持有互斥锁的线程 ID,如果该值大于 0,则表示互斥锁正在被占用,否则表示锁可用。

MutexLock()
  : holder_(0) {
  MCHECK(pthread_mutex_init(&mutex_, NULL));
}

~MutexLock() {
  assert(holder_ == 0);
  MCHECK(pthread_mutex_destroy(&mutex_));
}

MutexLock 在构造函数中初始化锁并将 holder_ 变量初始化为 0。在析构函数中先判断当前互斥锁是否被占用,如果没有被占用则销毁锁。

void unassignHolder() {
  holder_ = 0;
}

void assignHolder() {
  holder_ = CurrentThread::tid();
}

其中,unassignHolder() 用来释放线程句柄,assignHolder() 则用来分配线程句柄。

void lock() ACQUIRE() {
  MCHECK(pthread_mutex_lock(&mutex_));
  assignHolder();
}

void unlock() RELEASE() {
  unassignHolder();
  MCHECK(pthread_mutex_unlock(&mutex_));
}

lock() 用来加锁,其内部先对互斥锁加锁,然后分配线程句柄,以保证线程安全。而 unlock() 则用来解锁,先释放线程句柄,然后对互斥锁解锁。

MutexLockCondition 设置为其友元类,同时声明内嵌类 UnassignGuardCondition 使用。

class UnassignGuard : noncopyable {
public:
  explicit UnassignGuard(MutexLock& owner)
    : owner_(owner) {
    owner_.unassignHolder();
  }

  ~UnassignGuard() {
    owner_.assignHolder();
  }
private:
  MutexLock& owner_;
};

其中,UnassignGuard 在构造时释放线程句柄,而在析构时则分配线程句柄。

MutexLockGuard

MutexLockGuard 用于封装临界区的进入和退出,即加锁、解锁操作。其一般为栈上对象,作用域刚好等于临界区域。

同样地,MutexLockGuard 也使用了 RAII 手法,在构造函数中加锁,在析构函数中解锁。

explicit MutexLockGuard(MutexLock& mutex) ACQUIRE(mutex)
  : mutex_(mutex) {
  mutex_.lock();
}

~MutexLockGuard() RELEASE() {
  mutex_.unlock();
}

注意,在 MutexLockGuard 类内部只有一个成员变量:

MutexLock& mutex_;

这里使用 “引用” 的原因主要有两点:

  1. 对构造函数中传入的 MutexLock 对象本身进行加锁操作;
  2. MutexLockGuard 销毁时并不会销毁原先的互斥锁;

在类外,定义了如下宏:

#define MutexLockGuard(x) error "Missing guard object name"

其目的是为了防止生成一个无名的临时 MutexLockGuard 对象,因为该临时对象没有持久的作用域,不能长时间持有 MutexLock 对象。

Condition

Condition 类简单的对条件变量进行了封装。其成员变量有两个,分别是 mutex_pcond_,前者用来绑定互斥锁,后者用来作为条件变量。

同样的,Condition 也使用了 RAII 机制:

explicit Condition(MutexLock& mutex)
  : mutex_(mutex) {
  MCHECK(pthread_cond_init(&pcond_, NULL));
}

~Condition() {
  MCHECK(pthread_cond_destroy(&pcond_));
}

Condition 的阻塞等待如下:

void wait() {
  MutexLock::UnassignGuard ug(mutex_);
  MCHECK(pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()));
}

bool waitForSeconds(double seconds) {
  struct timespec abstime;
  clock_gettime(CLOCK_REALTIME, &abstime);

  const int64_t kNanoSecondsPerSecond = 1000000000;
  int64_t nanoseconds = static_cast<int64_t>(seconds * kNanoSecondsPerSecond);

  abstime.tv_sec += static_cast<time_t>((abstime.tv_nsec + nanoseconds) / kNanoSecondsPerSecond);
  abstime.tv_nsec = static_cast<long>((abstime.tv_nsec + nanoseconds) % kNanoSecondsPerSecond);

  MutexLock::UnassignGuard ug(mutex_);
  return ETIMEDOUT == pthread_cond_timedwait(&pcond_, mutex_.getPthreadMutex(), &abstime);
}

创建 UnassignGuard 对象的目的是为了和 pthread_cond_wait() 达到同样的效果,该函数通常有以下三步操作:

  1. 释放 Mutex;
  2. 阻塞等待;
  3. 当被唤醒时,重新获得 Mutex 并返回;

所以在此处构建 UnassignGuard 对象以释放线程句柄,而在 pthread_cond_wait() 内部则会释放互斥锁,阻塞等待互斥锁可用。当被唤醒时,pthread_cond_wait() 函数返回并对互斥锁上锁,此时析构 UnassignGuard 对象以分配线程句柄,重新获得互斥锁。此过程类似于 MutexLock::lock()MutexLock::unlock() 操作。

唤醒阻塞线程的方法如下:

void notify() {
  MCHECK(pthread_cond_signal(&pcond_));
}

void notifyAll() {
  MCHECK(pthread_cond_broadcast(&pcond_));
}

notify() 用于唤醒某个在条件变量上阻塞等待的另一个线程,notifyAll() 用于唤醒在当前条件变量上阻塞等待的所有线程。

注意,如果一个类要包含 MutexLockCondition,要注意它们的声明顺序和初始化顺序,MutexLock 应先于 Condition 构造,并作为后者的构造参数。

CountDownLatch

CountDonwLatch 是一个用于倒计时的同步辅助类,比如在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。既可以用于所有子线程等待主线程完成事务,也可以用于主线程等待子线程完成初始化工作。

该类与上方几个类的成员变量有所不同,并未采用 “引用” 的方式,说明互斥锁、条件变量仅为内部所有。其成员变量有如下三个:mutex_condition_count_。mutex_ 主要用于临界区的代码保护,condition_ 主要配合 mutex_ 使用,而 count_ 则用于表示计数器,当计数器为 0 时,通知所有阻塞等待的线程。

注意,mutex_ 变量被 mutable 关键字所修饰,该关键字用于表明被修饰变量在常成员函数内部也可以被修改。

int CountDownLatch::getCount() const {
  MutexLockGuard lock(mutex_);
  return count_;
}

由于在常成员函数 getCount() 内部需要对互斥锁加锁和解锁,所以需要 mutable 关键字来进行修饰。

向阻塞线程发送通知的线程调用如下函数,不断减少 count_ 的数量,直到其为 0,则通知所有阻塞线程。

void CountDownLatch::countDown() {
  MutexLockGuard lock(mutex_);
  --count_;
  if (count_ == 0) {
    condition_.notifyAll();
  }
}

而被阻塞线程则调用如下函数以进行等待操作,在 count_ 数量减为 0 之前,一直处于等待唤醒状态。

void CountDownLatch::wait() {
  MutexLockGuard lock(mutex_);
  while (count_ > 0) {
    condition_.wait();
  }
}

注意:所有线程对象都需要调用同一个 CountDownLatch 实例,公用同一个 MutexLockCondition

标签:__,Muduo,void,mu,原语,同步,线程,type,ptr
From: https://www.cnblogs.com/tuilk/p/16792088.html

相关文章

  • 利用for循环同步执行异步方法
    //定义一个异步函数constfoo1=(i)=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{console.log(I)resolve(i)},3000......
  • Muduo库之WeakCallback、Singleton
    WeakCallback在WeakCallback.h文件中定义了模板类WeakCallback,在其模板参数中,有一个可变模板参数ARGS,用以指示回调函数的参数。在类内部,定义有两个成员变量,分别是ob......
  • Muduo库之StringPiece、Time
    StringPiece在StringPiece.h文件中,声明了两个类类型,一个是StringArg,另一个是StringPiece,前者用于在传递函数参数时同时兼容C风格的字符串(constchar*)和C++风格......
  • Muduo库之ProcessInfo、Exception、FileUtil
    ProcessInfo在ProcessInfo.h文件中,存在一个命名空间ProcessInfo,其中声明了大部分进程需要使用到相关信息的函数,比如进程的PID、UID、EUID,主机名,进程状态,程状态等等。......
  • postgresql-主备同步模式测试
    测试1:synchronous_standby_names=*是否等同于synchronous_standby_names=any1(s1,s2,s3)结论:不等同ANY2(s1,s2):quorum,quorum,asyncANY1(s1,s2,s3):quorum,quorum,q......
  • 表同步更新的问题的触发器
      1sql server 2000 触发器,表同步更新的问题   2有三个表,A ,B,C  3A、B表中含有: A1,B1,C1 三个字段,  4C 表中存放A、B表中的A1、B1......
  • 世界局势风云变幻,经济战同步上演!加密货币发挥关键作用!
    现如今,俄乌冲突已成为二战以来发生在欧洲的最大军事冲突。世界局势风云变幻,围绕在武装战争的背后,伴随而来的经济战争也在同步上演。当前加密货币作为全球金融体系中不可忽视......
  • ogg不同版本之间同步
    环境:主库ogg版本:21C从库ogg版本:12C 同步遇到的问题:2022-10-1314:52:48ERROROGG-02598File/goldengate12c/dirdat/cp000000000,withtrailformatrelea......
  • iPhone备忘录莫名清空?用云同步备忘录可避免这种情况
    最近有多名iPhone用户在社交平台称,自己的iPhone备忘录内容被莫名清空了,并且在苹果的云端服务iCloud中也没有办法找回。对于这种情况,苹果的客户回应,如果有用户遇到类似情况......
  • mysql数据同步
    Navicat同步数据库中数据kettle实现mysql单表增量同步使用Kettle同步mysql数据,增量同步教程执行步骤Navicat定时同步数据库使用Kettle进行数据同步(增量)KETTLE安装及连接M......