Linux线程互斥
Posted 种花家de小红帽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux线程互斥相关的知识,希望对你有一定的参考价值。
目录
C++11线程
C++11的多线程,本质是pthread库的封装
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void thread_run()
while (1)
cout << "我是新线程..." << endl;
sleep(1);
int main()
thread t1(thread_run);
while (1)
cout << "我是主线程..." << endl;
sleep(1);
t1.join();
return 0;
C++线程封装
mythread.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <cassert>
#include <cstdlib>
class Thread;
class Context
public:
Thread* _this;
void* _args;
Context() : _this(nullptr), _args(nullptr)
~Context()
;
class Thread
typedef std::function<void*(void*)> func_t;
public:
Thread(func_t func, void* args, int number)
: _func(func), _args(args)
char buffer[128];
snprintf(buffer, sizeof(buffer), "thread-%d", number);
_name = buffer;
// start
Context* ctx = new Context();
ctx->_this = this;
ctx->_args = _args;
int n = pthread_create(&_tid, nullptr,start_routine, ctx);
assert(n == 0);
// 类内成员,有缺省参数
// 在类内创建线程,想让线程执行对应的方法,需要将方法设置成static
static void* start_routine(void* args)
Context* ctx = static_cast<Context*>(args);
void* ret = ctx->_this->run(ctx->_args);
delete ctx;
return ret;
// 静态方法不能调用成员方法或者成员变量
// return _func(_args);
void join()
int n = pthread_join(_tid, nullptr);
assert(n == 0);
void* run(void* args)
_func(args);
private:
std::string _name;
func_t _func;
void* _args;
pthread_t _tid;
;
mythread.cc
#include "mythread.hpp"
#include <unistd.h>
#include <memory>
void* thread_run(void* args)
std::string work_type = static_cast<const char*>(args);
while (true)
std::cout << "我是一个新线程, 我正在做: " << work_type << std::endl;
sleep(1);
int main()
std::unique_ptr<Thread> thread1(new Thread(thread_run, (void*)"hello thread1. ", 1));
std::unique_ptr<Thread> thread2(new Thread(thread_run, (void*)"hello thread2.. ", 2));
std::unique_ptr<Thread> thread3(new Thread(thread_run, (void*)"hello thread3... ", 3));
thread1->join();
thread2->join();
thread3->join();
return 0;
线程互斥
对变量进行++、--,在C、C++中一条语句,但在os中是至少三条指令:
1. 从内存读取数据到cpu寄存器中
2. 在寄存器对数据进行对应的算逻运算
3. 写回新的结果到内存中变量的位置
全局变量在没有保护的情况下,并不安全
多个执行流进行安全访问的共享资源 ------ 临界资源
多个执行流中访问临界资源的代码 ------ 临界区
想让多个线程串行访问共享资源 ------ 互斥
对一个资源进行访问的,要么不做,要么做完 ------- 原子性
#include "mythread.hpp"
#include <unistd.h>
#include <memory>
// 多个线程交叉执行的本质:让调度器尽可能频繁发生线程调度与切换
// 线程一般在什么时间发生切换呢?时间片到了,来了更高优先级的线程,线程等待的时候
// 线程检测一般是在什么时候呢?从内核态返回用户态的时候,线程要对调度状态进行检测,如果可以,就直接发送线程切换
// 共享资源, 火车票
int tickets = 10000;
void* thread_run(void* args)
std::string username = static_cast<const char*>(args);
while (true)
// 假设票数为 1 时
// 线程1将tickets=1写入寄存器中被切走
// 此时线程2也将tickets=1写入寄存器被切走,线程3、线程4类似
// 然后线程1进入if语句内,--tickets,线程2、线程3、线程4也依次 --tickets
// 故结果出现负值
if (tickets > 0)
usleep(1000); // 单位为微妙, 1 秒 == 1000 毫秒 == 1000 000 微妙 == 1000 000 000 纳秒
std::cout << username << " 正在抢票中: " << tickets << std::endl;
--tickets;
else
break;
int main()
std::unique_ptr<Thread> thread1(new Thread(thread_run, (void*)"user1", 1));
std::unique_ptr<Thread> thread2(new Thread(thread_run, (void*)"user2", 2));
std::unique_ptr<Thread> thread3(new Thread(thread_run, (void*)"user3", 3));
std::unique_ptr<Thread> thread4(new Thread(thread_run, (void*)"user4", 4));
thread1->join();
thread2->join();
thread3->join();
thread4->join();
return 0;
互斥锁
锁本身就是一个共享资源,加锁的过程也是原子的
锁申请成功,进入临界资源,其他线程阻塞等待
锁申请成功,访问临界资源时别切走,其他线程不能申请锁,也便无法向后执行
在使用锁的时候,保证粒度尽量小
加锁是同步行为,对公共资源保护的时候要所有线程都加锁
创建互斥锁的方法多样,底层原理还是以下三个函数接口
#include "mythread.hpp"
#include <unistd.h>
#include <memory>
#include <vector>
#define NUM 4
// 多个线程交叉执行的本质:让调度器尽可能频繁发生线程调度与切换
// 线程一般在什么时间发生切换呢?时间片到了,来了更高优先级的线程,线程等待的时候
// 线程检测一般是在什么时候呢?从内核态返回用户态的时候,线程要对调度状态进行检测,如果可以,就直接发送线程切换
class ThreadData
public:
ThreadData(const std::string& threadname, pthread_mutex_t* pmutex)
: _threadname(threadname), _pmutex(pmutex) ;
~ThreadData() ;
std::string _threadname;
pthread_mutex_t*_pmutex;
;
// 共享资源, 火车票
int tickets = 5000;
void* GetTicket(void* args)
// std::string username = static_cast<const char*>(args);
ThreadData* td = static_cast<ThreadData*>(args);
while (true)
// 加锁和解锁的过程是由多个线程串行执行,程序变慢
// 锁只规定互斥访问,没有规定由谁优先执行
// 锁就是真正的让多个执行流进行竞争的结果
pthread_mutex_lock(td->_pmutex);
// 上锁与解锁之间的代码就是临界区
if (tickets > 0)
usleep(1000); // 单位为微妙, 1 秒 == 1000 毫秒 == 1000 000 微妙 == 1000 000 000 纳秒
std::cout << td->_threadname << " 正在抢票中: " << tickets << std::endl;
--tickets;
pthread_mutex_unlock(td->_pmutex);
else
pthread_mutex_unlock(td->_pmutex);
break;
usleep(100); //模拟形成一个订单给用户
int main()
// 初始化锁
pthread_mutex_t lock;
pthread_mutex_init(&lock, nullptr);
std::vector<pthread_t> tids(NUM);
for (int i = 0; i < NUM; ++i)
char buffer[64];
snprintf(buffer, sizeof(buffer), "thread %d", i + 1);
ThreadData* td = new ThreadData(buffer, &lock);
pthread_create(&tids[i], nullptr, GetTicket, td);
for (const auto& tid: tids)
pthread_join(tid, nullptr);
// 释放锁
pthread_mutex_destroy(&lock);
return 0;
封装互斥锁
当对象被构建的时候自动加锁,当对象被析构的时候解锁
mymutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
class Mutex
public:
Mutex(pthread_mutex_t* plock = nullptr)
: _plock(plock) ;
void lock()
if (_plock) pthread_mutex_lock(_plock);
void unlock()
if (_plock) pthread_mutex_unlock(_plock);
private:
pthread_mutex_t* _plock;
;
class LockGuard
public:
LockGuard(pthread_mutex_t* mutex)
: _mutex(mutex)
_mutex.lock(); //在构造函数中进行加锁
;
~LockGuard()
_mutex.unlock(); //在析构函数中解锁
private:
Mutex _mutex;
;
mythread.cc
#include "mythread.hpp"
#include "mymutex.hpp"
#include <unistd.h>
#include <memory>
#include <vector>
#define NUM 4
// 多个线程交叉执行的本质:让调度器尽可能频繁发生线程调度与切换
// 线程一般在什么时间发生切换呢?时间片到了,来了更高优先级的线程,线程等待的时候
// 线程检测一般是在什么时候呢?从内核态返回用户态的时候,线程要对调度状态进行检测,如果可以,就直接发送线程切换
class ThreadData
public:
ThreadData(const std::string& threadname, pthread_mutex_t* pmutex)
: _threadname(threadname), _pmutex(pmutex) ;
~ThreadData() ;
std::string _threadname;
pthread_mutex_t*_pmutex;
;
// 共享资源, 火车票
int tickets = 2000;
void* GetTicket(void* args)
// std::string username = static_cast<const char*>(args);
ThreadData* td = static_cast<ThreadData*>(args);
while (true)
// 设置代码块
// 加锁和解锁的过程是由多个线程串行执行,程序变慢
// 锁只规定互斥访问,没有规定由谁优先执行
// 锁就是真正的让多个执行流进行竞争的结果
// pthread_mutex_lock(td->_pmutex);
LockGuard lock_guard(td->_pmutex);
// 上锁与解锁之间的代码就是临界区
if (tickets > 0)
usleep(1000); // 单位为微妙, 1 秒 == 1000 毫秒 == 1000 000 微妙 == 1000 000 000 纳秒
std::cout << td->_threadname << " 正在抢票中: " << tickets << std::endl;
--tickets;
// pthread_mutex_unlock(td->_pmutex);
else
// pthread_mutex_unlock(td->_pmutex);
break;
usleep(100); //模拟形成一个订单给用户
int main()
// 初始化锁
pthread_mutex_t lock;
pthread_mutex_init(&lock, nullptr);
std::vector<pthread_t> tids(NUM);
for (int i = 0; i < NUM; ++i)
char buffer[64];
snprintf(buffer, sizeof(buffer), "thread %d", i + 1);
ThreadData* td = new ThreadData(buffer, &lock);
pthread_create(&tids[i], nullptr, GetTicket, td);
for (const auto& tid: tids)
pthread_join(tid, nullptr);
// 释放锁
pthread_mutex_destroy(&lock);
return 0;
Linux学习_线程的互斥
线程的同步和互斥
同步和互斥的概念
- 线程同步
- 线程同步是一个宏观的概念,在微观上包含线程的相互排斥(注意和线程互斥的不一样)和线程先后执行的约束问题。
- 解决同步方式
- 条件变量
- 线程信号量
- 线程互斥
- 线程执行是相互排斥的,一个线程在操作共享资源时,其他线程是处于阻塞状态或等待状态,不可以操作共享资源。
- 和线程同步相互排斥的区别是:线程互斥不考虑线程先后执行的约束问题。
- 解决互斥方式
- 互斥锁
- 读写锁
- 线程信号量
保证同一时间操作共享资源的线程只有一个。
- ATM线程冲突示例
创建boy和girl线程同时对银行账户进行操作
主函数:
typedef struct
{
int code;
double balance;
}Account,*pAccount;
//取款金额amt
extern double withdraw(pAccount a, double amt)
{
if(amt<0 || amt> a->balance) return 0.0;
double balance = a->balance;
sleep(1);//模拟银行连接互联网取钱的延时
balance -= amt;
a->balance = balance;
return amt; //成功则返回取款金额
}
//------------------------------------------------------------------------
/*
* account_test.c
* 创建boy和girl线程同时对银行账户进行操作
*/
//头文件
//--------------------------------
#include "account.h"
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
//--------------------------------
//结构体
//--------------------------------
typedef struct
{
char name[20];
pAccount account;
double amt; //存取款金额
}OperArg;
//--------------------------------
//定义取款操作的线程运行函数
//--------------------------------
void* withdraw_fn(void* arg)
{
OperArg *oa = (OperArg*)arg;
double amt = withdraw(oa->account, oa->amt);
printf("%8s(0x%lx) withdraw %f from account %d\\n",\\
oa->name, pthread_self(), amt, oa->account->code);
return (void*)0;
}
//--------------------------------
//定义存款操作的线程运行函数
//--------------------------------
void* deposit_fn(void* arg)
{
OperArg *oa = (OperArg*)arg;
double amt = deposit(oa->account, oa->amt);
printf("%8s(0x%lx) deposit %f from account %d\\n",\\
oa->name, pthread_self(), amt, oa->account->code);
return (void*)0;
}
//--------------------------------
//定义检查银行账户的线程运行函数
//--------------------------------
void* check_fn(void* arg)
{
return (void*)0;
}
//--------------------------------
//--------------------------------
//主函数
//--------------------------------
int main(void)
{
int err;
pthread_t boy, girl;
pAccount a = create_account(100001, 10000);
OperArg o1, o2; //o1,o2分别代表操作的两个用户
strcpy(o1.name, "boy");
o1.account = a;
o1.amt = 10000;
strcpy(o2.name, "girl");
o2.account = a;
o2.amt = 10000;
//启动两个子线程(boy和girl)
//同时操作同一个银行账户
err = pthread_create(&boy, NULL,withdraw_fn, (void*)&o1);
if(err != 0) perror("pthread create error");
err = pthread_create(&girl, NULL,withdraw_fn, (void*)&o2);
if(err != 0) perror("pthread create error");
pthread_join(boy, NULL);
pthread_join(girl, NULL);
printf("account balance: %f\\n", get_balance(a));
destroy_account(a);
return 0;
}
输出:
girl(0xb657e460) withdraw 10000.000000 from account 100001
boy(0xb6d7f460) withdraw 10000.000000 from account 100001
account balance: 0.000000
男生女生同时取款,都取到了10000元,相当于两人一共取了20000,这是不对的。
此时线程不安全。
分析:boy girl 两者共同调用了withdraw函数,在栈中都有一个空间,girl在boy延时睡眠时进来,此时账户里余额并未改变,于是两人得到的balance都为10000。
线程互斥——互斥锁(互斥量)
- 概念:
- 互斥锁(mutex)是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则线程挂起,直到上锁的线程释放互斥锁为止。
- 互斥锁数据类型
- pthread_mutex_t
- 互斥锁的创建和销毁
#include <pthread.h>
int pthread_mutex_init(
pthread_mutex_t* restrict mutex,
const pthread_mutexattr_t* mutexattr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
- 返回:成功返回0,否则返回错误编号。
- 参数:
- mutex:互斥锁
- mutexattr:互斥锁创建方式(一般写NULL,默认互斥锁类型)
- PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
- PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁
- PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁
- 互斥锁上锁和解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
功能:上锁,拿不到锁则阻塞
int pthread_mutex_trylock(pthread_mutex_t* mutex);
功能:上锁,拿不到锁则返回出错信息
int pthread_mutex_unlock(pthread_mutex_t* mutex);
功能:释放锁
返回:成功返回0,否则返回错误编号。
对ATM示例进行加锁
/*account.c
*/
typedef struct
{
int code;
double balance;
//定义一把互斥锁,用来对多个线程操作的银行账户(共享资源)进行加锁(保护)
//建议一把互斥锁绑定一个账户,尽量不设置成全局变量,否则可能出现一把锁去锁几百个银行账户导致并发性降低
pthread_mutex_t mutex;
}Account,*pAccount;
#include "account.h"
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
//创建账户
extern pAccount create_account(int code, double balance)
{
pAccount a = (pAccount)malloc(sizeof(Account));
assert(a != NULL);
a->code = code;
a->balance = balance;
//对互斥锁进行初始化
pthread_mutex_init(&a->mutex, NULL);
return a;
}
//销毁账户
extern void destroy_account(pAccount a)
{
assert(a != NULL);
//销毁互斥锁
pthread_mutex_destroy(&a->mutex);
free(a);
}
//取款金额amt
extern double withdraw(pAccount a, double amt)
{
assert(a != NULL);
//以下对共享资源从上锁到释放的代码区称为
//对共享资源操作的临界区,犹如一个禁区
//对共享资源(账户)加锁
pthread_mutex_lock(&a->mutex);
if(amt<0 || amt> a->balance)
{
//释放互斥锁
pthread_mutex_unlock(&a->mutex);
return 0.0;
}
double balance = a->balance;
sleep(1);//模拟银行连接互联网取钱的延时
balance -= amt;
a->balance = balance;
//释放互斥锁
pthread_mutex_unlock(&a->mutex);
return amt; //成功则返回取款金额
}
//存款
extern double deposit(pAccount a, double amt)
{
assert(a != NULL);
//对共享资源(账户)加锁
pthread_mutex_lock(&a->mutex);
if(amt<0){
//释放互斥锁
pthread_mutex_unlock(&a->mutex);
return 0.0;
}
double balance = a->balance;
sleep(1);
balance += amt;
a->balance = balance;
//释放互斥锁
pthread_mutex_unlock(&a->mutex);
return amt;
}
//查看账户余额
extern double get_balance(pAccount a)
{
assert(a != NULL);
//对共享资源(账户)加锁
pthread_mutex_lock(&a->mutex);
double balance = a->balance;
//释放互斥锁
pthread_mutex_unlock(&a->mutex);
return balance;
}
给每个线程加上锁,再运行主函数
输出:
boy(0xb6dc5460) withdraw 10000.000000 from account 100001
girl(0xb65c4460) withdraw 0.000000 from account 100001
account balance: 0.000000
- 互斥锁属性创建和销毁
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
- 返回:成功返回0,否则返回错误编号
- 参数:attr:互斥锁属性
- 互斥锁进程共享属性操作
#include <pthread.h>
int pthread_mutexattr_getpshared(
const pthread_mutexattr_t* restrict attr,
int* restrict pshared);
int pthread_mutexattr_setpshared(
pthread_mutexattr_t* attr,
int pshared);
- 返回:成功返回0,否则返回错误编号
- 参数:
- attr:互斥锁属性
- pshared:进程共享属性
- PTHREAD_PROCESS_PRIVATE(默认情况)
- 锁只能用于一个进程内部的多个线程进行互斥
- PTHREAD_PROCESS_SHARED
- 锁可以用于两个不同进程中的线程进行互斥
- PTHREAD_PROCESS_PRIVATE(默认情况)
- 互斥锁类型操作
#include <pthread.h>
int pthread_mutexattr_gettype(
const pthread_mutexattr_t* restrict attr,
int* restrict type);
int pthread_mutexattr_settype(
pthread_mutexattr_t* attr,
int type);
- 返回:成功返回0,否则返回错误编号
- 参数:
- attr:互斥锁属性
- type:互斥锁类型
- 标准互斥锁:PTHREAD_MUTEX_NORMAL
- 第一次上锁成功,第二次上锁会阻塞。
- 递归互斥锁:PTHREAD_MUTEX_RECURSIVE
- 第一次上锁成功,第二次以后上锁还是成功,内部计数。
- 检错互斥锁:PTHREAD_MUTEX_ERRORCHECK
- 第一次上锁成功,第二次上锁会出错。
- 默认互斥锁:PTHREAD_MUTEX_DEFAULT(同标准互斥锁)
- 标准互斥锁:PTHREAD_MUTEX_NORMAL
示例
/*
* lock_type.c
* 互斥锁类型
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
pthread_mutex_t mutex;
if(argc<2){
printf("-usage:%s [error|normal|recursive]\\n",argv[0]);exit(1);
}
//定义互斥锁属性
pthread_mutexattr_t mutexattr;
//初始化互斥锁属性
pthread_mutexattr_init(&mutexattr);
//check argv传入的互斥锁类型
if(!strcmp(argv[1],"error")){
//设置互斥锁类型
pthread_mutexattr_settype(&mutexattr,\\
PTHREAD_MUTEX_ERRORCHECK);
}
else if(!strcmp(argv[1],"normal")){
pthread_mutexattr_settype(&mutexattr,\\
PTHREAD_MUTEX_NORMAL);
}
else if(!strcmp(argv[1],"recursive")){
pthread_mutexattr_settype(&mutexattr,\\
PTHREAD_MUTEX_RECURSIVE);
}
//按指定类型初始化互斥锁
pthread_mutex_init(&mutex,&mutexattr);
//上锁
if(pthread_mutex_lock(&mutex)!=0)
printf("lock failure\\n");
else
printf("lock success\\n");
//第二次上锁
if(pthread_mutex_lock(&mutex)!=0)
printf("lock failure\\n");
else
printf("lock success\\n");
//上两次锁,也需要释放两次
pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex);
//销毁互斥锁和属性
pthread_mutexattr_destroy(&mutexattr);
pthread_mutex_destroy(&mutex);
return 0;
}
输出:
$ ./lock_type error
lock success
lock failure
$ ./lock_type recursive
lock success
lock success
$ ./lock_type normal
lock success
^C
$
线程互斥——读写锁
- 概念
- 线程使用互斥锁缺乏读并发性。
- 当读操作较多,写操作较少时,可使用读写锁提高线程读并发性。
- 读写锁数据类型
- pthread_rwlock_t
- 读写锁创建和销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,\\
const pthread_rwlockattr_t* restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
- 返回:成功返回0,否则返回错误编号
- 参数:
- rwlock:读写锁
- attr:读写锁属性
- 读写锁加锁和解锁
#include <pthread.h>
//功能:加读锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
//功能:加写锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
//功能:释放锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
- 返回:成功返回0,否则返回错误编号
- 参数:
- rwlock:读写锁
- 示例:
/*
* rwlock_feature.c
*
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定义读写锁
pthread_rwlock_t rwlock;
int main(int argc, char **argv)
{
if(argc<3){
printf("-usage:%s [r|w] [r|w]\\n",argv[0]);
exit(1);
}
//读写初始化
pthread_rwlock_init(&rwlock,NULL);
if(!strcmp("r",argv[1])){
//加读锁
if(pthread_rwlock_rdlock(&rwlock) != 0)
{printf("first read lock failure\\n");}
else
{printf("first read lock success\\n");}
}
if(!strcmp("w",argv[1])){
//加写锁
if(pthread_rwlock_wrlock(&rwlock) != 0)
{printf("first write lock failure\\n");}
else
{printf("first write lock success\\n");}
}
if(!strcmp("r",argv[2])){
//加读锁
if(pthread_rwlock_rdlock(&rwlock) != 0)
{printf("second read lock failure\\n");}
else
{printf("second read lock success\\n");}
}
if(!strcmp("w",argv[2])){
//加写锁
if(pthread_rwlock_wrlock(&rwlock) != 0)
{printf("second write lock failure\\n");}
else
{printf("second write lock success\\n");}
}
//释放读写锁
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
return 0;
}
输出:
$ ./rwlock_feature r r
first read lock success
second read lock success
$ ./rwlock_feature r w
first read lock success
^C
$ ./rwlock_feature w w
first write lock success
second write lock failure
$ ./rwlock_feature w r
first write lock success
second read lock failure
分析:
- 读和读:不排斥
- 读和写\\写和读:排斥(后一个线程阻塞或失败)
- 写和写:排斥
以上是关于Linux线程互斥的主要内容,如果未能解决你的问题,请参考以下文章
(转载)Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
Linux下线程同步(带你了解什么是互斥锁死锁读写锁条件变量信号量等)