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.多线程如何避免死锁

  1. 加锁顺序(线程按照一定的顺序加锁)
  2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  3. 死锁检测
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.

 

posted @ 2022-02-13 16:29  dsfsadfdgd  阅读(65)  评论(0)    收藏  举报