线程安全,一切麻烦从共享资源开始
多个线程访问共享资源(全局和静态)的时候会冲突
三个概念:原子性、可见性和顺序性
原子性:
一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)
CPU执行指令:读取指令、读取内存、执行指令、写回内存
i++ 1)从内存中读取i的值; 2)把i+1; 3)把结果写回内存
可见性:
当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到
cpu有调整缓存。每个线程读取共享变量时,会将变量从内存加载到cpu的缓存中,修改该变量后,cpu会立即更
新缓存,但不一定会立即将它写回内存。此时其它线程访问该变量,从内存中读到的是旧数据,而非第一个线程
更新后的数据
顺序性:
程序执行的顺序按照代码的先后顺序执行
cpu为了提高程序整体的执行效率,可能会对代码进行优化,按照更高效的顺序执行代码
cpu虽然并不保证完全按照代码顺序执行,但它保证程序最终的执行结果与代码顺序执行的结果一致。
如何解决线程安全问题
volatile关键字(解决不了多线程修改同一变量的问题)
保证了变量的内存可见性,cpu每次从内存中读取,不缓存
禁止代码重排序。
因为不是原子的
原子操作
本质是总线锁
三条汇编指令:xadd、cmpxchg或xchg
硬件级别的锁
线程同步
线程同步,各种锁的使用
互斥锁
只有加锁和解锁操作,确保同一时间只有一个线程访问共享资源
访问共享资源之前加锁,访问完成后释放锁
如果某线程持有锁,其它的线程形成等待队列
属性有:PTHREAD_MUTEX_TIMED_NP缺省值,普通锁,保证资源分配的公平性
PTHREAD_MUTEX_RECURSIVE_NP嵌套锁,允许同一个线程对同一个锁成功获得多次
PTHREAD_MUTEX_ADAPTIVE_NP适应锁,解锁后,请求锁的线程重新竞争
自旋锁
互斥锁在等待锁的时候,线程会休眠不消耗CPU,而自旋锁则有一个循环不断地检查锁是否可以使用,消耗CPU
两者没有好坏之分,只是场景不一样。自旋锁适用于等待很短的使用场景,而互斥锁适用于可能等待比较长的场景
所以自旋锁没有提供超时的函数,因为使用此锁是基于等待很短的使用场景,否则用互斥锁
读写锁
读写锁允许更高的并发性
三种状态:读模式加锁(读锁),写模式加锁(写锁),不加锁
特点:
只要没有线程持有写锁,任意线程都可以成功申请读锁
只有在不加锁状态时,才能成功申请写锁
注意事项:
读定你起码适合于对读的次数远大于写的情况
linux系统优先考虑读锁,这种实现方式有可能导致写入线程饿死的情况
pthread_rwlock_t mutex
int pthread_rwlock_init()
int pthread_rwlock_destroy();
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_IINITIALIZER
int pthread_rwlock_rdlock()
int pthread_rwlock_tryrdlock()
int pthread_rwlock_timedrdlock()
条件变量
与互斥锁一起使用
实现生产消费者模型
实现通知的功能
信号量
一个整数计数器,其数值用于表示空闲临界资源的数量
申请资源时,信号量减少,表示可用资源数减少
释放资源时,信号量增加,表示可用资源数增加
生产消费者模型
1.条件变量+互斥锁实现生产消费者模型
pthread_cond_wait(&cond, &mutex)
把互斥锁解锁-->阻塞,等待条件(被唤醒)-->条件被触发+给互斥锁加锁
2.信号量实现生产消费者模型
互斥锁
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int var=0;
//线程同步-互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *thmain(void *arg); //线程主函数
int main()
{
//pthread_mutex_init(&mutex, NULL); //初始化互斥量 PTHREAD_MUTEX_INITIALIZER有这个则不需要调用此方法初始化
pthread_t thid1=0, thid2=0;
if(pthread_create(&thid1, NULL, thmain, NULL) != 0) {printf("thid1 faile\n"); exit(-1);}
if(pthread_create(&thid2, NULL, thmain, NULL) != 0) {printf("thid2 faile\n"); exit(-1);}
//等待子线程退出
printf("join...\n");
pthread_join(thid1, NULL);
pthread_join(thid2, NULL);
printf("join ok.\n");
printf("var=%d\n", var);
pthread_mutex_destroy(&mutex); //主进程退出的时候销毁互斥量
return 0;
}
void *thmain( void *arg)
{
for(int i=0; i <100000; i++)
{
pthread_mutex_lock(&mutex);
var++;
pthread_mutex_unlock(&mutex);
//__sync_fetch_and_add(&var,1); //原子操作
//printf("pthread=%d\n", pthread_self());
}
}
自旋锁
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int var=0;
//线程同步-自旋锁
pthread_spinlock_t spin;
void *thmain(void *arg); //线程主函数
int main()
{
pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE);
pthread_t thid1=0, thid2=0;
if(pthread_create(&thid1, NULL, thmain, NULL) != 0) {printf("thid1 faile\n"); exit(-1);}
if(pthread_create(&thid2, NULL, thmain, NULL) != 0) {printf("thid2 faile\n"); exit(-1);}
//等待子线程退出
printf("join...\n");
pthread_join(thid1, NULL);
pthread_join(thid2, NULL);
printf("join ok.\n");
printf("var=%d\n", var);
pthread_spin_destroy(&spin); //主进程退出的时候销毁自旋锁
return 0;
}
void *thmain( void *arg)
{
for(int i=0; i <100000; i++)
{
pthread_spin_lock(&spin);
var++;
pthread_spin_unlock(&spin);
//__sync_fetch_and_add(&var,1); //原子操作
//printf("pthread=%d\n", pthread_self());
}
}
读写锁
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <signal.h>
using namespace std;
/*
读写锁允许更高的并发性
三种状态:读模式加锁(读锁),写模式加锁(写锁),不加锁
特点:
只要没有线程持有写锁,任意线程都可以成功申请读锁
只有在不加锁状态时,才能成功申请写锁
注意事项:
读定你起码适合于对读的次数远大于写的情况
linux系统优先考虑读锁,这种实现方式有可能导致写入线程饿死的情况
*/
int var=0;
//线程同步-读写锁
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //声明读写锁并初始化
void *thmain(void *arg); //线程主函数
void handle(int sig); //信号15的处理函数
int main()
{
signal(15, handle); //设置信号15的处理函数
pthread_t thid1=0, thid2=0, thid3=0;
if(pthread_create(&thid1, NULL, thmain, NULL) != 0) {printf("thid1 faile\n"); exit(-1);}
if(pthread_create(&thid2, NULL, thmain, NULL) != 0) {printf("thid2 faile\n"); exit(-1);}
if(pthread_create(&thid3, NULL, thmain, NULL) != 0) {printf("thid3 faile\n"); exit(-1);}
//等待子线程退出
printf("join...\n");
pthread_join(thid1, NULL);
pthread_join(thid2, NULL);
pthread_join(thid3, NULL);
printf("join ok.\n");
pthread_rwlock_destroy(&rwlock); //主进程退出的时候销毁锁
return 0;
}
void *thmain( void *arg)
{
for(int i=0; i <1000; i++)
{
printf("线程%lu开始申请读锁...\n", pthread_self());
pthread_rwlock_rdlock(&rwlock); //加锁
printf("线程%lu开始申请读锁成功\n", pthread_self()); //3个线程都可以申请到读锁,
sleep(5);
pthread_rwlock_unlock(&rwlock);
printf("线程%lu己释放读锁。\n", pthread_self());
if( i == 4)
{
//sleep(5); //如果这里不sleep,3个线程则一直占着读锁,那写锁一直申请不成功
//只有在不加锁状态时,才能成功申请写锁
}
}
}
void handle(int sig)
{
printf("开始申请写锁...\n");
pthread_rwlock_wrlock(&rwlock); //加锁
printf("申请写锁成功.\n");
sleep(10);
pthread_rwlock_unlock(&rwlock); //有写锁,如果这里不解锁,则上面就无法再申请到读锁
}