概述
该文件中包含windows和linux中各种锁的实现方式(分开编译),部分是经过验证的,注释详细;需要源码可以@我
/*====================================
* file: ws_thread_mutex.h 文件
* anchor: wensheng
* date: 2016-04-04
* info: 线程锁对象
* log: 1. 2016-04-04 wensheng create
======================================*/
//* 除了使用这个头文件的锁之外,可以使用boost中锁
//* boost::mutex 中包含详细的各种锁:
//* boost::mutext(独占互斥锁)
//* boost::shared_mutex(共享互斥锁(一写多读))
//* boost::recursive_mutex(递归式互斥锁(最多允许一个线程锁定(一个线程可以多次锁定)))
//* boost::timed_mutex(超时锁)
#ifndef MUDUO_BASE_MUTEX_H
#define MUDUO_BASE_MUTEX_H
#include "../h/types.h"
#include "ws_thread_type.h"
#include "ws_thread_rwlock.h" // 共享锁
#ifdef WS_WINDOWS
// windows下包括用户方式下原子锁,关键代码段;内核方式下的事件内核对象,可等待计时器,互斥量,信号量
namespace ws_thread
{
/*=============================================================================================*/
// 原子操作
/*=============================================================================================*/
// 原子操作(T只能是long int),因为windows下32位和64位实现的函数不一样,若需要扩展自己去winnt.h中查找更换函数
template<class T = int32_t>
class AtomDo
{
public:
//加值,返回加后的结果
static T& add_fetch(T& ptr, T value = 1) { return (T)InterlockedExchangeAdd(&ptr, value); }
//减值,返回减后的结果
static T& sub_fetch(T& ptr, T value = 1) { return (T)InterlockedExchangeAdd(&ptr, -value); }
//或值,返回或后的结果
static T& or_fetch(T& ptr, T value = 1) { T out = InterlockedOr(&ptr, value); return (out | value); }
//与值,返回与后的结果
static T& and_fetch(T& ptr, T value = 1) { T out = InterlockedAnd(&ptr, value); return (out & value); }
//异或值,返回异或后的结果
static T& xor_fetch(T& ptr, T value = 1) { T out = InterlockedXor(&ptr, value); return (out ^ value); }
//加值,返回加前的结果
static T& fetch_add(T& ptr, T value = 1) { T out = InterlockedExchangeAdd(&ptr, value); return (out - value); }
//减值,返回减前的结果
static T& fetch_sub(T& ptr, T value = 1) { T out = InterlockedExchangeAdd(&ptr, -value); return (out + value); }
//或值,返回或前的结果
static T& fetch_or(T& ptr, T value = 1) { return (T)InterlockedOr(&ptr, value); }
//与值,返回与前的结果
static T& fetch_and(T& ptr, T value = 1) { return (T)InterlockedAnd(&ptr, value); }
//异或值,返回异或前的结果
static T& fetch_xor(T& ptr, T value = 1) { return (T)InterlockedXor(&ptr, value); }
//设置到指定值,返回设置前的结果
static T& fetch_set(T& ptr, T value = 1) { return (T)InterlockedExchange(&ptr, value); }
//ptr==oldValue,就将ptr设置为newValue;返回初始值
static T compare_swap(T& ptr, T oldValue, T newValue) { return (T)InterlockedCompareExchange(&ptr, newValue, oldValue); }
private:
AtomDo() {}
};
/*=============================================================================================*/
// 互斥锁(内核方式)
/*=============================================================================================*/
// 互斥锁(sleep_waiting,闲等待锁(睡眠))
struct ws_mutex
{
public:
ws_mutex();
virtual ~ws_mutex();
void lock();
bool trylock();
void unlock();
std::unique_lock<std::mutex> m_Mutex;
size_t pid;
};
typedef struct ws_mutex WS_MUTEX;
// 锁操作
class MutexLock
{
public:
MutexLock(WS_MUTEX* pMutex);
virtual ~MutexLock();
bool TryLock(WS_MUTEX* pMutex);//手动锁
private:
WS_MUTEX* m_mutex_lock;
};
/*=============================================================================================*/
// 读写共享锁
/*=============================================================================================*/
// 读锁
struct RW_Lock_R
{
public:
RW_Lock_R(CRWLockRecur* a_rw);
virtual ~RW_Lock_R();
private:
CRWLockRecur* m_rw_lock;
};
// 写锁
struct RW_Lock_W
{
public:
RW_Lock_W(CRWLockRecur* a_rw);
virtual ~RW_Lock_W();
private:
CRWLockRecur* m_rw_lock;
};
/*=============================================================================================*/
// 临界区(因为自旋锁操作比较复杂,这里提供一种简单的临界区锁功能)
/*=============================================================================================*/
struct scopedlock
{
public:
scopedlock();
virtual ~scopedlock();
void lock();
void unlock();
CRITICAL_SECTION m_ScopedLock;
};
typedef struct scopedlock SCOPED;
// 锁操作
class Scopedlock
{
public:
Scopedlock(SCOPED* pscopedlock);
virtual ~Scopedlock();
private:
SCOPED* m_scoped_lock;
};
/*=============================================================================================*/
// 事件内核对象(实现互斥锁的功能,注意该锁是锁定当前线程,但是解锁却是随机的)
/*=============================================================================================*/
struct EventLock
{
public:
EventLock();
virtual ~EventLock();
void lock();
void unlock();
HANDLE m_EventLock;
};
#ifdef WINDDK
/*=============================================================================================*/
// 自旋锁,需要自己下载安装wdk包才行
/*=============================================================================================*/
// 自旋锁(busy-waiting,忙等待锁)
// 只在内核可抢占或SMP内核架构的机器才有效
// 使用注意事项(否则会出现死锁): 1.临界区代码不能有睡眠,2.锁区域处理时间不能太长,3.有(软/硬)中断的地方建议使用屏蔽中断的函数
// 锁
typedef struct splock
{
public:
splock();
void lock();
void unlock();
KSPIN_LOCK m_SpinLock;
KIRQL oldIrql;
} SPINLOCK;
// 锁操作
class Apanlock
{
public:
Apanlock(SPINLOCK* pSpinLock, bool isbh = false);
virtual ~Apanlock();
private:
SPINLOCK* m_spin_lock;
};
#endif
}
#else
namespace ws_thread
{
/*=============================================================================================*/
// 原子操作
/*=============================================================================================*/
// 原子操作(T只能是int,long,long long,和unsigned)
template<class T = int32_t>
class AtomDo
{
public:
//加值,返回加后的结果
static T& add_fetch(T& ptr, T value = 1) { return (T)__sync_add_and_fetch(&ptr, value); }
//减值,返回减后的结果
static T& sub_fetch(T& ptr, T value = 1) { return (T)__sync_sub_and_fetch(&ptr, value); }
//或值,返回或后的结果
static T& or_fetch(T& ptr, T value = 1) { return (T)__sync_or_and_fetch(&ptr, value); }
//与值,返回与后的结果
static T& and_fetch(T& ptr, T value = 1) { return (T)__sync_and_and_fetch(&ptr, value); }
//异或值,返回异或后的结果
static T& xor_fetch(T& ptr, T value = 1) { return (T)__sync_xor_and_fetch(&ptr, value); }
//加值,返回加前的结果
static T& fetch_add(T& ptr, T value = 1) { return (T)__sync_fetch_and_add(&ptr, value); }
//减值,返回减前的结果
static T& fetch_sub(T& ptr, T value = 1) { return (T)__sync_fetch_and_sub(&ptr, value); }
//或值,返回或前的结果
static T& fetch_or(T& ptr, T value = 1) { return (T)__sync_fetch_and_or(&ptr, value); }
//与值,返回与前的结果
static T& fetch_and(T& ptr, T value = 1) { return (T)__sync_fetch_and_and(&ptr, value); }
//异或值,返回异或前的结果
static T& fetch_xor(T& ptr, T value = 1) { return (T)__sync_fetch_and_xor(&ptr, value); }
//设置到指定值,返回设置前的结果
static T& fetch_set(T& ptr, T value = 1) { return (T)__sync_lock_test_and_set(&ptr, value); }
//ptr==oldValue,就将ptr设置为newValue;返回初始值
static T compare_swap(T& ptr, T oldValue, T newValue) { return (T)__sync_val_compare_and_swap(&ptr, oldValue, newValue); }
private:
AtomDo() {}
};
/*=============================================================================================*/
// 互斥锁
/*=============================================================================================*/
// 互斥锁(sleep_waiting,闲等待锁(睡眠))
struct ws_mutex
{
public:
ws_mutex();
virtual ~ws_mutex();
void lock();
bool trylock();
void unlock();
pthread_mutex_t m_mutex;
size_t pid; // 线程id
};
typedef struct ws_mutex WS_MUTEX;
// 条件变量(与互斥锁联合使用,防止锁长时间消耗CPU)
// 在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
struct ws_cond
{
public:
ws_cond(WS_MUTEX* a_mutex);
virtual ~ws_cond();
int wait(); // 阻塞,0表示成功
int wait(uint64_t a_time); // 阻塞指定时间,0表示成功
int signal();// 解除阻塞,0表示成功
int broadcast();// 解除所有阻塞,0表示成功
pthread_cond_t m_cond;
WS_MUTEX* m_ws_mutex;
};
typedef struct ws_cond WS_COND;
// 锁操作
class MutexLock
{
public:
explicit MutexLock(WS_MUTEX* pMutex, WS_COND* pCond = NULL); // explicit 指明参数不能含隐式转换
virtual ~MutexLock();
bool TryLock(WS_MUTEX* pMutex);//手动锁
int Wait(uint64_t a_time = 0); // 阻塞,a_time=0表示不指定时间阻塞
int unWait(bool all = false); // 解除阻塞,all= true,表示解除所有阻塞
private:
WS_MUTEX* m_mutex_lock;
WS_COND* m_cond;
bool m_locked;
};
/*=============================================================================================*/
// 读写共享锁
/*=============================================================================================*/
// 读锁
struct RW_Lock_R
{
public:
explicit RW_Lock_R(CRWLockRecur* a_rw);
virtual ~RW_Lock_R();
private:
CRWLockRecur* m_rw_lock;
};
// 写锁
struct RW_Lock_W
{
public:
explicit RW_Lock_W(CRWLockRecur* a_rw);
virtual ~RW_Lock_W();
private:
CRWLockRecur* m_rw_lock;
};
//-------------------------------------------------
// linux 下另一种实现方法
//-------------------------------------------------
struct rwlock_t
{
public:
rwlock_t();
virtual ~rwlock_t();
void wrlock();
void rdlock();
void unlock();
pthread_rwlock_t rwlock_;
};
struct RW_Lock
{
public:
enum ltype
{
wrlock = 0, // 写锁
rdlock = 1, // 读锁
};
RW_Lock(rwlock_t &rwlock, ltype type = wrlock);
virtual ~RW_Lock();
void lock(ltype type = wrlock);
void unlock();
rwlock_t* lock_;
bool locked_;
};
/*=============================================================================================*/
// 自旋锁
/*=============================================================================================*/
// 自旋锁(busy-waiting,忙等待锁)
// 只在内核可抢占或SMP内核架构的机器才有效
// 使用注意事项(否则会出现死锁): 1.临界区代码不能有睡眠,2.锁区域处理时间不能太长,3.有(软/硬)中断的地方建议使用屏蔽中断的函数
// 锁
struct ws_splock
{
public:
ws_splock();
void lock();
bool trylock();
void unlock();
// 以下只在内核模式下可使用
void lockirq(); // 屏蔽中断
bool trylockirq();
void unlockirq();
pthread_spinlock_t m_SpinLock;
};
typedef struct ws_splock SPINLOCK;
// 锁操作
class Apanlock
{
public:
explicit Apanlock(SPINLOCK* pSpinLock, bool isbh = false); //isbh 是不是屏蔽中断
virtual ~Apanlock();
bool TryLock(SPINLOCK* pSpinLock, bool isbh = false);//手动锁, isbh 是不是屏蔽中断
private:
SPINLOCK* m_spin_lock;
bool m_isbh; // 是不是屏蔽硬中断(同时屏蔽软中断)
};
}
#endif
#endif
-----------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------
/*====================================
* file: ws_thread_mutex.h 文件
* anchor: wensheng
* date: 2016-04-04
* info: 线程锁对象
* log: 1. 2016-04-04 wensheng create
======================================*/
#include "ws_thread_mutex.h"
#ifdef WS_WINDOWS
using namespace ws_thread;
#ifdef WINDDK
/*=============================================================================================*/
// 自旋锁,需要自己下载安装wdk包才行
/*=============================================================================================*/
// 自旋锁(busy-waiting,忙等待锁)
// 只在内核可抢占或SMP内核架构的机器才有效
// 使用注意事项(否则会出现死锁): 1.临界区代码不能有睡眠,2.锁区域处理时间不能太长,3.有(软/硬)中断的地方建议使用屏蔽中断的函数
splock::splock() { KeInitializeSpinLock(m_SpinLock); }
void splock::lock()
{
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
KeAcquireSpinLock(m_SpinLock, &oldIrql);
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
}
void splock::unlock()
{
KeReleaseSpinLock(m_SpinLock, oldIrql);
}
// 锁操作
Apanlock::Apanlock(SPINLOCK* pSpinLock, bool isbh) //isbh 是不是屏蔽中断
{
pSpinLock->lock();
m_spin_lock = pSpinLock;
}
virtual Apanlock::~Apanlock()
{
m_spin_lock->unlock();
}
#endif
/*=============================================================================================*/
// 互斥锁(内核方式)
/*=============================================================================================*/
// 互斥锁(sleep_waiting,闲等待锁(睡眠))
ws_mutex::ws_mutex()
{
}
ws_mutex::~ws_mutex()
{
}
void ws_mutex::lock()
{
m_Mutex.lock();
pid = GetCurrentThreadId();
}
bool ws_mutex::trylock()
{
bool ret = m_Mutex.try_lock();
pid = GetCurrentThreadId();
return ret;
}
void ws_mutex::unlock()
{
pid = 0;
m_Mutex.unlock();
}
MutexLock::MutexLock(WS_MUTEX* pMutex)
{
m_mutex_lock = pMutex;
if (NULL != pMutex)
{
pMutex->lock();
}
}
MutexLock::~MutexLock()
{
if (NULL != m_mutex_lock)
{
m_mutex_lock->unlock();
}
}
bool MutexLock::TryLock(WS_MUTEX* pMutex)
{
m_mutex_lock = pMutex;
if (NULL != pMutex)
{
return pMutex->trylock();
}
return false;
}
/*=============================================================================================*/
// 读写共享锁
/*=============================================================================================*/
// 读锁
RW_Lock_R::RW_Lock_R(CRWLockRecur* a_rw)
{
m_rw_lock = a_rw;
a_rw->ShareLock();
}
RW_Lock_R::~RW_Lock_R()
{
m_rw_lock->UnShareLock();
}
// 写锁
RW_Lock_W::RW_Lock_W(CRWLockRecur* a_rw)
{
m_rw_lock = a_rw;
a_rw->OwnLock();
}
RW_Lock_W::~RW_Lock_W()
{
m_rw_lock->UnOwnLock();
}
/*=============================================================================================*/
// 临界区(因为自旋锁操作比较复杂,这里提供一种简单的临界区锁功能)
/*=============================================================================================*/
scopedlock::scopedlock()
{
InitializeCriticalSection(&m_ScopedLock);
}
scopedlock::~scopedlock()
{
DeleteCriticalSection(&m_ScopedLock);
}
void scopedlock::lock()
{
EnterCriticalSection(&m_ScopedLock);
}
void scopedlock::unlock()
{
LeaveCriticalSection(&m_ScopedLock);
}
// 锁操作
Scopedlock::Scopedlock(SCOPED* pscopedlock)
{
m_scoped_lock = pscopedlock;
m_scoped_lock->lock();
}
Scopedlock::~Scopedlock()
{
m_scoped_lock->unlock();
}
/*=============================================================================================*/
// 事件内核对象(实现互斥锁的功能,注意该锁是锁定当前线程,但是解锁却是随机的)
/*=============================================================================================*/
EventLock::EventLock()
{
m_EventLock = CreateEvent(NULL, TRUE, FALSE, NULL);
}
EventLock::~EventLock()
{
CloseHandle(m_EventLock);
}
void EventLock::lock()
{
WaitForSingleObject(m_EventLock, INFINITE); // 阻塞
}
void EventLock::unlock()
{
PulseEvent(m_EventLock); // 随机解除锁定,线程本身再变为未通知状态,遇到WaitForSingleObject会再次阻塞
}
#else /* linux */
using namespace ws_thread;
/*=============================================================================================*/
// 互斥锁
/*=============================================================================================*/
// 互斥锁(sleep_waiting,闲等待锁(睡眠))
ws_mutex::ws_mutex()
{
pthread_mutexattr_t mutexattr;
//MCHECK(pthread_mutex_init(&m_mutex, &mutexattr));
pthread_mutex_init(&m_mutex, &mutexattr);
}
ws_mutex::~ws_mutex()
{
//MCHECK(pthread_mutex_destroy(&m_mutex));
pthread_mutex_destroy(&m_mutex);
}
void ws_mutex::lock()
{
//MCHECK(pthread_mutex_lock(&m_mutex));
pthread_mutex_lock(&m_mutex);
pid = pthread_self();
}
bool ws_mutex::trylock()
{
int ret= pthread_mutex_trylock(&m_mutex);
pid = pthread_self();
return ret;
}
void ws_mutex::unlock()
{
pid = (pid != pthread_self()) ? pid : 0;
//MCHECK(0 == pid);
//MCHECK(pthread_mutex_unlock(&m_mutex));
pthread_mutex_unlock(&m_mutex);
}
// 条件变量(与互斥锁联合使用,防止锁长时间消耗CPU)
// 在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
ws_cond::ws_cond(WS_MUTEX* a_mutex)
{
m_ws_mutex = a_mutex;
pthread_condattr_t cattr;
//MCHECK(pthread_cond_init(&m_cond, &cattr));
pthread_cond_init(&m_cond, &cattr);
}
ws_cond::~ws_cond()
{
//MCHECK(pthread_cond_destroy(&m_cond));
pthread_cond_destroy(&m_cond);
}
// 阻塞,0表示成功
int ws_cond::wait()
{
return pthread_cond_wait(&m_cond, &(m_ws_mutex->m_mutex));
}
// 阻塞指定时间,0表示成功
int ws_cond::wait(uint64_t a_time)
{
timespec to;
to.tv_sec = time(NULL) + a_time; to.tv_nsec = 0;
return pthread_cond_timedwait(&m_cond, &(m_ws_mutex->m_mutex), &to);
}
// 解除阻塞,0表示成功
int ws_cond::signal()
{
return pthread_cond_signal(&m_cond);
}
// 解除所有阻塞,0表示成功
int ws_cond::broadcast()
{
return pthread_cond_broadcast(&m_cond);
}
// 锁操作
MutexLock::MutexLock(WS_MUTEX* pMutex, WS_COND* pCond)
{
m_mutex_lock = pMutex;
m_cond = pCond;
if (NULL != pMutex)
{
pMutex->lock();
}
}
MutexLock::~MutexLock()
{
if (NULL != m_mutex_lock)
{
m_mutex_lock->unlock();
}
}
//手动锁
bool MutexLock::TryLock(WS_MUTEX* pMutex)
{
m_mutex_lock = pMutex;
if (NULL != m_mutex_lock)
{
return m_mutex_lock->trylock();
}
return false;
}
// 阻塞,a_time=0表示不指定时间阻塞
int MutexLock::Wait(uint64_t a_time)
{
if (NULL != m_cond)
{
if (0 == a_time)
{
return m_cond->wait();
}
return m_cond->wait(a_time);
}
return -1;
}
// 解除阻塞,all= true,表示解除所有阻塞
int MutexLock::unWait(bool all)
{
if (NULL != m_cond)
{
if (false == all)
{
return m_cond->signal();
}
return m_cond->broadcast();
}
return -1;
}
/*=============================================================================================*/
// 读写共享锁
/*=============================================================================================*/
// 读锁
RW_Lock_R::RW_Lock_R(CRWLockRecur* a_rw)
{
m_rw_lock = a_rw;
a_rw->ShareLock();
}
RW_Lock_R::~RW_Lock_R()
{
m_rw_lock->UnShareLock();
}
// 写锁
RW_Lock_W::RW_Lock_W(CRWLockRecur* a_rw)
{
m_rw_lock = a_rw;
a_rw->OwnLock();
}
RW_Lock_W::~RW_Lock_W()
{
m_rw_lock->UnOwnLock();
}
//-------------------------------------------------
// linux 下另一种实现方法
//-------------------------------------------------
rwlock_t::rwlock_t()
{
pthread_rwlockattr_t attr;
while (pthread_rwlockattr_init(&attr));
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); //设置写优先
while (pthread_rwlock_init(&rwlock_, &attr));
}
rwlock_t::~rwlock_t()
{
pthread_rwlock_destroy(&rwlock_);
}
void rwlock_t::wrlock()
{
pthread_rwlock_wrlock(&rwlock_);
}
void rwlock_t::rdlock()
{
pthread_rwlock_rdlock(&rwlock_);
}
void rwlock_t::unlock()
{
pthread_rwlock_unlock(&rwlock_);
}
RW_Lock::RW_Lock(rwlock_t &rwlock, ltype type)
{
lock_ = &rwlock;
locked_ = false;
lock(type);
}
RW_Lock::~RW_Lock()
{
unlock();
}
void RW_Lock::lock(ltype type)
{
if (locked_) return; // 防止一个锁包含重复锁
if (type == wrlock)
{
lock_->wrlock();
locked_ = true;
}
else if (type == rdlock)
{
lock_->rdlock();
locked_ = true;
}
}
void RW_Lock::unlock()
{
if (locked_)
{
lock_->unlock();
locked_ = false;
}
}
/*=============================================================================================*/
// 自旋锁
/*=============================================================================================*/
// 自旋锁(busy-waiting,忙等待锁)
// 只在内核可抢占或SMP内核架构的机器才有效
// 使用注意事项(否则会出现死锁): 1.临界区代码不能有睡眠,2.锁区域处理时间不能太长,3.有(软/硬)中断的地方建议使用屏蔽中断的函数
ws_splock::ws_splock()
{
// 只在同一个进程间使用,用PTHREAD_PROCESS_PRIVATE
pthread_spin_init(&m_SpinLock, PTHREAD_PROCESS_PRIVATE);
}
void ws_splock::lock()
{
MCHECK(pthread_spin_lock(&m_SpinLock));
}
bool ws_splock::trylock()
{
return pthread_spin_trylock(&m_SpinLock);
}
void ws_splock::unlock()
{
MCHECK(pthread_spin_unlock(&m_SpinLock));
}
void ws_splock::lockirq()
{
//MCHECK(spin_lock_irq(&m_SpinLock));
}
bool ws_splock::trylockirq()
{
return false;//spin_trylock_irq(&pSpinLock);
}
void ws_splock::unlockirq()
{
//pin_unlock_irq(m_SpinLock);
}
Apanlock::Apanlock(SPINLOCK* pSpinLock, bool isbh) //isbh 是不是屏蔽中断
{
if (!isbh)
{
pSpinLock->lock();
}
else
{
pSpinLock->lockirq();
}
m_spin_lock = pSpinLock;
m_isbh = isbh;
}
Apanlock::~Apanlock()
{
if (NULL != m_spin_lock)
{
if (!m_isbh)
{
m_spin_lock->unlock();
}
else
{
m_spin_lock->unlockirq();
}
}
m_spin_lock = NULL;
}
// 手动锁
bool Apanlock::TryLock(SPINLOCK* pSpinLock, bool isbh)//isbh 是不是屏蔽中断
{
bool ret = false;
if (!isbh)
{
ret = pSpinLock->trylock();
}
else
{
ret = pSpinLock->trylockirq();
}
if (ret)
{
m_spin_lock = pSpinLock;
m_isbh = isbh;
}
else
{
m_spin_lock = NULL;
}
return ret;
}
#endif
最后
以上就是落寞草莓为你收集整理的线程同步(8):linux和Windows各种锁实现案例的全部内容,希望文章能够帮你解决线程同步(8):linux和Windows各种锁实现案例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复