1.信号量
操作 | 函数 |
---|---|
创建 | int sem_init(sem_t *sem, int pshared, unsigned int value) |
销毁 | int sem_destroy(sem_t *sem) |
阻塞等待 | int sem_wait(sem_t *sem) |
非阻塞等待 | int sem_trywait(sem_t * sem) |
触发 | int sem_post(sem_t *sem) |
1.1 创建
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数说明:
参数 | 含义 |
---|---|
sem | 信号量对象 |
pshared | 信号量类型。0:线程共享;<0:进程共享 |
value | 初始值 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败 |
1.2 销毁
int sem_destroy(sem_t *sem);
参数说明:
参数 | 含义 |
---|---|
sem | 信号量对象 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败 |
1.3 等待
1.3.1 阻塞等待
int sem_wait(sem_t *sem);
参数说明:
参数 | 含义 |
---|---|
sem | 信号量对象 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败 |
1.3.2 非阻塞等待
int sem_trywait(sem_t * sem);
参数说明:
参数 | 含义 |
---|---|
sem | 信号量对象 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败 |
1.4 触发
int sem_post(sem_t *sem);
参数说明:
参数 | 含义 |
---|---|
sem | 信号量对象 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败 |
示例:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>
using namespace std;
int n = 0;
void Output(sem_t* psem){
for(int i=0;i<1000;++i){
sem_wait(psem);
cout << "func" << pthread_self() << "[" << ++n << "]" <<endl;
sem_post(psem);
}
}
int main(){
sem_t sem;
sem_init(&sem,0,1); //参数为0表示为线程信号量。第三个参数为1表示为二值信号量
cout << "main thread" << pthread_self() << endl;
pthread_t tid;
pthread_create(&tid,NULL,reinterpret_cast<void*(*)(void*)>(Output),&sem) //强制类型转换
;
cout << "new thread:" << tid << endl;
Output(&sem);
pthread_join(tid,NULL);
sem_destroy(&sem);
cout << "process exit" << endl;
}
案例:
竞争量:
#include <stdio.h>
#include <pthread.h>
void* func(void* arg){
printf("enter func\n");
sleep(1);
printf("do something\n");
sleep(1);
printf("level func\n");
}
int main(int argc,int argv[]){
pthread_t tids[3];
int i;
for(i=0;i<3;i++){
pthread_create(&tids[i],NULL,func,&mutex);
}
for(i=0;i<3;i++){
pthread_join(tids[i],NULL);
}
}
解决竞争量:
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
void* func(void* arg){
sem_wait(arg);
printf("enter func\n");
sleep(1);
printf("do something\n");
sleep(1);
printf("level func\n");
sem_post(arg);
}
int main(int argc,int argv[]){
sem_t sem;
sem_init(&sem,0,1);
pthread_t tids[3];
int i;
for(i=0;i<3;i++){
pthread_create(&tids[i],NULL,func,&sem);
}
for(i=0;i<3;i++){
pthread_join(tids[i],NULL);
}
sem_destroy(&sem);
}
2.互斥量
2.1 分类
2.2 操作
注意:使用pthread_cleanup,保证线程正常或者异常退出都能释放互斥锁。
2.3 基本套路
在线程处理函数中使用互斥量的基本套路
pthread_cleanup_push(pthread_mutex_unlock,pmutex);
pthread_mutex_lock(pmutex);
// do something
pthread_exit(0); // 退出才能触发clean_up
pthread_cleanup_pop(0);
2.4 信号量与互斥量的区别
3 条件变量
3.1 分类实现
分类 | 实现 |
---|---|
静态分配条件变量 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; |
动态分配静态变量 | pthread_cond_init(&cond, NULL);pthread_cond_destroy(&cond); |
3.2 操作
操作 | 函数 |
---|---|
条件等待 | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) |
计时等待 | int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) |
单个激活 | int pthread_cond_signal(pthread_cond_t *cond) |
全部激活 | int pthread_cond_broadcast(pthread_cond_t *cond) |
3.2.1 等待
条件等待:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
参数说明:
参数 | 含义 |
---|---|
cond | 条件变量 |
mutex | 互斥锁 |
计时等待:
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
参数说明:
参数 | 含义 |
---|---|
cond | 条件变量 |
mutex | 互斥锁 |
abstime | 等待时间 |
返回值说明:
返回值 | 含义 |
---|---|
ETIMEDOUT | 超时结束等待 |
3.2.2 激活
单个激活:
参数说明:
参数 | 含义 |
---|---|
cond | 条件变量 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
正数 | 错误码 |
全部激活:
int pthread_cond_broadcast(pthread_cond_t *cond)
参数说明:
参数 | 含义 |
---|---|
cond | 条件变量 |
返回值说明:
返回值 | 含义 |
---|---|
0 | 成功 |
正数 | 错误码 |
3.3 使用惯例
条件变量一般与互斥锁一起使用。
条件变量发送信号:
thread_mutex_lock(&mutex);
// do something
if(判断条件){
pthread_cond_signal(&cond);// 唤醒单个
// 或者
pthread_cond_broadcast(&cond);// 唤醒多个
}
pthread_mutex_unlock(&mutex);
条件变量等待信号:
pthread_mutex_lock(&mutex);
while(判断条件){
pthread_cond_wait(&cond,&mutex);
}
// do something
// 把判断条件改为false
pthread_mutex_unlock(&mutex);
3.4 互斥锁案例
抢票机制:
主线程放票时上锁,放20张票,然后去掉锁,让五个新建的线程行资源抢夺,如果没票的情况下,线程会进入停等状态,等待主线程继续放票出来。如果票被抢光时,线程退出
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int num = 0; //票的数量
int count = 5; //放票的次数
void* GetTicket(void*){
while(true){
//记录资源释放操作
pthread_cleanup_push((void(*)(void*))pthread_mutex_unlock,&mutex);
pthread_mutex_lock(&mutex);
while(num<=0){
if(0==num && 0==count){
cout << pthread_self() << "exit" << endl;
pthread_exit(0);
}
pthread_cond_wait(&cond,&mutex); //两个作用:
} //1.不满足条件,进入等待(阻塞),并且自动释放互斥锁
--num; //2.等待board_cast/single信号,重新抢夺互斥锁
cout << pthread_self() << "取走一张票,还剩" << num << "张票" << endl;
pthread_mutex_unlock(&mutex);
usleep(100000);
pthread_cleanup_pop(0); //调用pthread_exit()后线程退出自动释放资源
}
}
int main(){
num = 100;
pthread_t tids[5];
for(int i=0;i<5;++i){
pthread_create(tids+i,NULL,GetTicket,NULL);
}
//主线程放票
while(count--){
pthread_mutex_lock(&mutex);
num+=20;
cout << "主线程放票20张,现存" << num <<"票" << endl;
pthread_mutex_unlock(&mutex);
pthread_cond_broadcast(&cond); //唤醒所有在wait线程
//pthread_cond_single(&cond); //唤醒其中一个在wait的线程
sleep(1); //模拟耗时操作
}
for(int i=0;i<5;++i){
pthread_join(tids[i],NULL);
}
}
用c++11来实现:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
int main(){
int n = 0;
int count = 5;
mutex m;
condition_variable cv;
thread ts[5];
for(auto& t:ts){
t = thread([&]{
while(true){
{ //定义锁的作用域
unique_lock<mutex> lock(m);
cv.wait(lock,[&]{return n>0||0==n&&0==count;}); //条件变量,对应上面
if(n==0&&count==0){
cout << this_thread::get_id() << " exit" << endl;
}
cout << this_thread::get_id() << ":get a ticket,remin" << --n << endl;
}
this_thread::sleep_for(100ms); //将延时操作放在锁外
}
});
}
while (count){
{
unique_lock<mutex> lock(m);
n+=10;
--count;
cout << this_thread::get_id() << ":publish 10 tickets,remain" << n << endl;
cv.notify_all(); //唤醒所有等待线程
// cv.notify_one(); //唤醒所有等待线程中的一个
}
this_thread::sleep_for(1s);
}
for(auto& t:ts){
t.join();
}
}
4.读写锁
资源访问分为两种情况:读操作和写操作。
读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。
1.当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞;
2.当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞;
3.当读写锁在读模式锁状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞;
这种锁适用对数据结构进行读的次数比写的次数多的情况下,因为可以进行读锁共享。
共享独占:
读取锁(共享)
写入锁(独占)
4.1分类
分类 | 实现 |
---|---|
静态分配条件变量 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER |
动态分配静态变量 | pthread_rwlock_init(&rwlock, NULL);pthread_rwlock_destroy(&rwlock); |
4.2 加锁
4.2.1 读取锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
4.2.2 写入锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
4.3 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)