锁
1.基于redis实现分布式锁
setnx key val:根据返回值判断加锁是否成功。锁的value值为一个随机生成的UUID,释放锁的时候进行判断。
127.0.0.1:6379> setnx lck 123 (integer) 1 127.0.0.1:6379> setnx lck 123 (integer) 0
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
del key:删除key
2.基于mysql实现数据库锁
利用select … where … for update 排他锁
注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。
mysql数据库锁是非可重入的,因为同一个线程在没有释放锁之前无法再次获得锁,因为数据库中已经存在同一份记录了。想要实现可重入锁,可以在数据库中添加一些字段,比如获得锁的主机信息、线程信息等,那么在再次获得锁的时候可以先查询数据,如果当前的主机信息和线程信息等能被查到的话,可以直接把锁分配给它。
3.lock_guard和unique_lock
lock_guard
只能在析构函数中解锁,用{}包裹代码块,代码块外自动解锁。unique_lock
可以手动解锁加锁。
class LogFile { std::mutex _mu; ofstream f; public: LogFile() { f.open("log.txt"); } ~LogFile() { f.close(); } void shared_print(string msg, int id) { { lock_guard<mutex> guard(_mu); //do something 1 }//自动解锁 //do something 2 { lock_guard<mutex> guard(_mu);//再次加锁 // do something 3 } } };
class LogFile { std::mutex _mu; ofstream f; public: LogFile() { f.open("log.txt"); } ~LogFile() { f.close(); } void shared_print(string msg, int id) { std::unique_lock<std::mutex> guard(_mu); //do something 1 guard.unlock(); //临时解锁 //do something 2 guard.lock(); //继续上锁 // do something 3 // 这句话可要可不要,不写,析构的时候也会自动执行 // guard.ulock(); }
4.锁种类
互斥锁:
线程挂起来等待锁,避免消耗CPU,但带来了内核态和用户态切换和上下文切换开销。Futex机制是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,还是需要陷入内核并挂起任务。简单的说,futex就是通过在用户态的检查,如果发现没有锁竞争就不用陷入内核了。
在用户态使用原子操作,对持有锁的持有情况进行判断,如果锁可以占用,那么更新锁的状态并直接占用,不需要进入内核态,如果锁已经占用,则进入内核态挂起当前任务。
读写锁:
读锁共享,写锁互斥,读写锁互斥,大IO操作适合读写锁。
自旋锁:
线程需要锁时会一直轮询消耗CPU,适合锁持有时间短,线程不挂起,避免换上下文带来的损耗。
原子锁:
硬件层面实现,atomic,适合单个变量的数学运算。
mutex实现时利用Linux的futex:在用户态检查是否存在锁竞争,存在竞争时再进行系统调用来wait等待锁,无竞争时不需要陷入内核来获取锁。
可重入锁: recursive_mutex
同一个线程可以重复加锁,匹配相同次数的解锁才能使线程释放锁,其他线程加锁会形成死锁,锁内部计数器。
5. 条件变量:
线程同步:条件变量,允许线程阻塞在某个条件上,当满足条件时被唤醒。
wait可能会被虚假唤醒,需要在唤醒后重新判断条件,一般用while(条件){wait(&cond, &mutex);}。wait后会释放锁,可以被生产者重新加锁。
6 .单例模式
class single { private: single(){}; static single* instance; public: static single* get_instance(){ if(instance==NULL){ lock() if(instance==NULL){ instance=new instance(); } unlock(); } return instance; } };
7. sleep和wait区别
sleep不释放锁,让出cpu
wait 释放锁, 让出cpu。使用notify方法通知其他等待中的线程去获取锁。
8.多线程如何避免死锁
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测
Thread 1: lock A lock B Thread 2: wait for A lock C (when A locked) Thread 3: wait for A wait for B wait for C
9.线程/进程同步方法:
1. 读写锁
2、互斥量: 为协调共同对一个共享资源的单独访问而设计的。 Mutex,只有一个线程访问资源。(Windows和Linux都有)
3、信号量: 为控制一个具有有限数量用户资源而设计。 PV操作,限制对资源的使用的线程数量,多个线程访问资源。信号量可以说是一种资源计数器。
4、条件变量: 用来通知线程有一些事件已发生,从而启动后继任务的开始。
进程同步方法
信号量
无锁CAS
文件锁
bool compare_and_swap(int *reg,int oldval,int newval)
{
int reg_val = *reg;
if(reg_val == oldval)
{
*reg = newval;
return true;
}
return false;
}
10. pthread_cond_wait()
在使用pthread_cond_wait(),必须有三步:
1:加互斥锁:pthread_mutex_lock(&__mutex)
2:等待:pthread_cond_wait(&__cond, &__mutex) //解锁->等待->加锁
3:解互斥锁:pthread_mutex_unlock(&__mutex)
发送信号量时,也要有三步:
1:加互斥锁:pthread_mutex_lock(&__mutex)
2:发送:pthread_cond_signal(&__cond)
3:解互斥锁:pthread_mutex_unlock(&__mutex)
demo
pthreadCondWait.c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /*初始化互斥锁*/ pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量 void *thread1(void *); void *thread2(void *); int i=1; int main(void) { pthread_t t_a; pthread_t t_b; pthread_create(&t_a,NULL,thread1,(void *)NULL);/*创建进程t_a*/ pthread_create(&t_b,NULL,thread2,(void *)NULL); /*创建进程t_b*/ pthread_join(t_b, NULL);/*等待进程t_b结束*/ pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); exit(0); } void *thread1(void *junk) { for(i=1;i<=9;i++) { pthread_mutex_lock(&mutex);// if(i%3==0) pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/ else printf("thead1:%d\n",i); pthread_mutex_unlock(&mutex);//*解锁互斥量*/ printf("Up Unlock Mutex\n"); sleep(1); } } void *thread2(void *junk) { while(i<9) { pthread_mutex_lock(&mutex); if(i%3!=0) pthread_cond_wait(&cond,&mutex);/*等待*/ printf("thread2:%d\n",i); pthread_mutex_unlock(&mutex); printf("Down Ulock Mutex\n"); sleep(1); } }
11. 无锁队列
1. RingBuffer
适用于单消费单生产者场景,队列内部包括数据缓冲区,队首和队尾索引和队列长度,
对于单生产者和单消费者场景,由于read_index(队首索引)和write_index(队尾索引)都只会有一个线程写,因此不需要加锁也不需要原子操作,只需要判断队列满或者空,然后直接修改。
线程对write_index和read_index的读写操作如下:
(1)写操作。先判断队列时否为满,如果队列未满,则先写数据,写完数据后再修改write_index。
(2)读操作。先判断队列是否为空,如果队列不为空,则先读数据,读完再修改read_index。
2. LockFreeQueue
适用于多生产者多消费者场景
内部使用spinlock锁和开辟共享内存。LockFreeQueue并非无锁,只是使用了自旋锁。
队列内部的自旋锁结构体:
typedef struct { int m_lock; inline void spinlock_init() { m_lock = 0; } inline void spinlock_lock() { while(!__sync_bool_compare_and_swap(&m_lock, 0, 1)) {} } inline void spinlock_unlock() { __sync_lock_release(&m_lock); } } spinlock_t;
12.