Linux之互斥锁、死锁、读写锁、条件变量、生产者消费者模型

1.互斥锁简介

        在任何平台下,都会牵扯到锁这一概念,其实在任何平台下的锁都是差不多的。这里的互斥锁,线程A和线程B访问共享资源,要先获得锁,如果锁被占用,则加锁不成功需要等待对方释放锁,若锁没有被占用,则获得锁成功加锁,然后操作共享资源,操作完成后,必须解锁,同理B也是和A一样。也就是说不能有两个进程访问共享资源。

2.互斥锁的使用

互斥锁的一般使用步骤如下:

1)创建一把锁 pthread_mutex_t mutex

2)初始化 调用pthread_mutex_init(&mutex,NULL)

3)加锁和解锁 pthread_mutex_lock(&mutex) pthread_mutex_unlock(&mutex)

4)销毁锁 pthread_mutex_destroy(&mutex)

下面看一个需要用到互斥锁的例子:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<pthread.h>

pthread_mutex_t mutex;//创建为全局变量
int num=0;//共享资源
void *mythread1()
{   
    int i=0;
    int n=0;
    for(i=0;i<100000;i++)
    {   
        pthread_mutex_lock(&mutex);
        n=num;
        n++;
        num=n;
        printf("thread %d number:%d \n",pthread_self(),num);
        pthread_mutex_unlock(&mutex);
    }
}

void *mythread2()
{
    int i=0;
    int n=0;
    for(i=0;i<100000;i++)
    {   
        pthread_mutex_lock(&mutex);
        n=num;
        n++;
        num=n;
        printf("thread %d number:%d \n",pthread_self(),num);
        pthread_mutex_unlock(&mutex);
    }   
}

int main()
{
    pthread_mutex_init(&mutex,NULL);//初始化
    pthread_t thread1;
    int ret=pthread_create(&thread1,NULL,mythread1,NULL);
    if(ret<0)
    {   
        printf("pthread_create error ![%s]\n",strerror(ret));
        return -1; 
    }
    pthread_t thread2;
    ret=pthread_create(&thread2,NULL,mythread2,NULL);
    if(ret<0)
    {
        printf("pthread_create error ![%s]\n",strerror(ret));
        return -1;
    }
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);

    //最后记得销毁
    pthread_mutex_destroy(&mutex);

}

        首先创建的互斥锁需要是全局变量,因为互斥锁需要在不同的线程中被使用。互斥锁的初始化一定要在主线程中实现,这里的num是共享变量,可以被线程访问,但是在同一时刻只能被一个线程访问,因此需要在两个子线程中加锁,确保一时刻只能有一个线程拿到锁并且访问这个全局变量,最后使用完互斥锁记得要手动销毁。这个按照步骤来很简单的。

3.死锁及其解决方法

        死锁的产生有很多种情况,首先来看一下第一种情况,自己锁自己。

        可以看到,这里对mutex加锁了两次,就是一种典型的自己锁自己,在实际中可能是第一段锁上锁之后,中间有一段很长的代码,由于忽略了已经加过了锁,又在后面对其加锁造成这种结果,这种结果很危险的,会直接让程序卡死。

        测试了好几次,基本上都是卡死的状态。第二种产生死锁的情况就是进程一已经拿到了一个锁,又去争着拿另一个锁。其实就是获得锁的顺序不对。

pthread_mutex_t mutexA;
pthread_mutex_t mutexB;
void *mythread1()
{
    while(1)
    {
        pthread_mutex_lock(&mutexA);
        pthread_mutex_lock(&mutexB);
        printf("hello\n");
        sleep(rand()%3);
        printf("world\n");
        pthread_mutex_unlock(&mutexA);
        pthread_mutex_unlock(&mutexB);
        sleep(rand()%3);
    }   
}       
        
void *mythread2()
{       
    while(1)
    {
        pthread_mutex_lock(&mutexB);
        pthread_mutex_lock(&mutexA);
        printf("HELLO\n");
        sleep(rand()%3);
        printf("WORLD\n");
        pthread_mutex_unlock(&mutexA);
        pthread_mutex_unlock(&mutexB);
        sleep(rand()%3);
    }
    
}

        初始化和前面是一样的,这里定义了两个锁mutexA和mutexB,两个线程对A和B锁的上锁顺序不同, 在某一个时刻,进程一拿到了A锁,这时候进程二拿到了B锁,进程一又试着去拿B锁,进程二又试着去拿A锁,这样就陷入了一种循环等待,两个线程就卡住了。情况如下。

        对于上面这种情况,我们可以先释放自己的锁再去请求别的锁,避免使用这中嵌套的锁,让两个线程按照一定的顺序加锁:

        都按照先对A加锁,后对B加锁的顺序,就不会出现问题,最后一个就是可以使用pthread_mutex_trylock()函数来尝试加锁,这个函数是一个非阻塞函数,就是如果它没有拿到锁,就会去干点其他的,不会阻塞等待。

        都改成trylock也是可以的,程序可以正常运行,不会产生死锁。死锁不是Linux提供给开发者的一种机制,而是由于开发者操作不当引起的。

4.读写锁

        读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。读写锁使用场合:读的次数大于写的次数。 

读写锁的特性:读写锁是“写模式加锁“时,解锁前,所有对该锁加锁的线程都会被阻塞。

读写锁是”读模式加锁“时,如果线程以读模式对其加锁会成功,如果线程以写模式加锁会失败。

读写锁是”读模式加锁“时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求,优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高。

总之就是读并行,写独占,当读写同时等待锁的时候优先级高。

下面来看看读写锁相关函数:

pthread_rwlock_t rwlock;//创建一个读写锁

pthread_rwlock_init(&rwlock,NULL);//初始化读写锁

加锁:

pthread_rwlock_wrlock(&rwlock);//加写锁

pthread_rwlock_rdlock(&rwlock);//加读锁

解锁:pthread_rwlock_unlock(&rwlock);

销毁锁:pthread_rwlock_destroy(&rwlock);

这个过程和互斥锁基本上是一样的,按照这个步骤来就行。

下面看一个例子:创建三个写线程,五个读线程,写线程实现对共享资源的增加并输出,读线程只对共享数据进行读取。对写线程加写锁,对读线程加读锁。保证数据读取到的是最后读取到的。

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

pthread_rwlock_t rwlock;
int num=0;
void *thread_write(void *arg)
{
    int i = *(int *)arg;
    int cur;
    while(1)
    {
        //加写锁
        pthread_rwlock_wrlock(&rwlock);
        cur=num;
        cur++;
        num=cur;
        printf("[%d]--W [%d]\n",i,cur);
        //解锁
        pthread_rwlock_unlock(&rwlock);
        usleep(500);
    }   
}       
//读线程回调函数
void *thread_read(void *arg)
{
    int i = *(int*)arg;
    int cur;
    while(1)
    {
        //加读锁
        pthread_rwlock_rdlock(&rwlock);
        cur=num;
        printf("[%d]--R [%d]\n",i,cur);
        //解锁
        pthread_rwlock_unlock(&rwlock);
        usleep(500);
    }   
}       
int main()
{   
    int i=0;
    int n=8;
    //初始化
    pthread_rwlock_init(&rwlock,NULL);
    //创建三个写子线程
    int arr[8];
    pthread_t thread[8];
    for (i=0;i<3;i++)
    {
        arr[i]=i;
        pthread_create(&thread[i],NULL,thread_write,&arr[i]);
    }
    //创建五个读子线程
    for (i=3;i<n;i++)
    {
        arr[i]=i;
        pthread_create(&thread[i],NULL,thread_read,&arr[i]);
    }

    int j=0;
    for(j=0;j<n;j++)
    {
        pthread_join(thread[j],NULL);
    }
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

        读写锁的使用就和互斥锁的使用基本是一样的,会使用互斥锁,读写锁肯定就会使用。

        由运行结果可以看出读取到的是最后写入的数据。

5.条件变量介绍

        条件变量本身不是锁,但是也可以造成线程阻塞;通常配合互斥锁一起使用。使用条件变量可以使线程阻塞,等待某个条件的发生,当满足条件时,解除阻塞。

6.条件变量相关函数

        pthread_cond_t cond;//条件变量定义

        phtread_cond_init(&cond,NULL);//条件变量初始化

        pthread_cond_destroy(&cond);//条件变量销毁

        pthread_cond_wait(&cond,&mutex);//条件不满足,引起线程阻塞并解锁,条件满足,解除线程阻塞并加锁;第一个参数是条件变量,第二个参数是互斥量pthread_mutex_t mutex;

        pthread_cond_signal(&cond);//唤醒一个阻塞在该条件变量上的线程.

7.链表实现生产者消费者模型

        生产者:利用单链表的头插法不断生产节点,当生产了一个节点就会通知消费者消费节点。

        消费者:先判断单链表是否为空,为空则阻塞解锁,不为空则加锁消费节点。这里消费者生产者会存在一个相互唤醒的情况,当生产者生产一个节点的时,会通知消费者消费,当消费者线程检测到单链表为空时,就会通知生产者线程要开始生产节点了。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/types.h>
#include<time.h>

typedef struct Node{
    int data;
    struct Node *next;
}Node;

Node *head=NULL;
pthread_mutex_t mutex;
pthread_cond_t cond;

//生产者线程
void *producer()
{
    while(1)
    {   
        Node*node=malloc(sizeof(Node));
        if(node==NULL)
            return ;
        node->data=rand()%100;
        node->next=NULL;
        pthread_mutex_lock(&mutex);
        node->next=head;
        head=node;
        printf("P [%d]\n",node->data);  
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
        sleep(1);
    }   
}
//消费者线程
void *consumer()
{
    while(1)
    {   
        pthread_mutex_lock(&mutex);
        if(head==NULL)
        {   
            pthread_cond_wait(&cond,&mutex);
        }   
        printf("C [%d]\n",head->data);
        Node*cur=head;
        head=cur->next;
        pthread_mutex_unlock(&mutex);
        free(cur);
        cur=NULL;
        sleep(1);
    }
}

int main()
{
    srand(time(NULL));
    pthread_mutex_init(&mutex,NULL);//初始化互斥锁
    pthread_cond_init(&cond,NULL);//初始化条件变量

    pthread_t thread1;
    pthread_t thread2;
    int ret;
    ret=pthread_create(&thread1,NULL,producer,NULL);//创建生产者线程
    if(ret<0)
    {
        printf("pthread_create[%s] error\n",strerror(ret));
        return -1;
    }
    ret=pthread_create(&thread2,NULL,consumer,NULL);//创建消费者线程
    if(ret<0)
    {
        printf("pthread_create[%s] error\n",strerror(ret));
        return -1;
    }
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

         根据下面的结果,每生产一个节点救会消费一个节点。

 8.多线程在执行过程中被core掉的问题

        当上面的消费者和生产者有多个时,一个生产者生产一个节点,但是能够通过pthread_cond_signal将多个消费者全部唤醒,而只有一个消费者能够消费一个节点,消费完后,head变为NULL,其余的消费者中会有一个拿到锁,然后读取head的内容,就会被core掉。

        这种程序就像一个定时炸弹一样,刚开始的时候运行的好好的,不经意之间突然就崩溃了。这里的问题肯定是在消费者那里,也就是说当第一个消费者消费完之后,要及时进行判断head是否为空,如果为空,我们就为其解锁,让其跳过这次消费,让生产者得到锁,开始生产,就能解决这个问题。

        完整的代码就是对上面的生产者消费者进行简单修改:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/types.h>
#include<time.h>

typedef struct Node{
    int data;
    struct Node *next;
}Node;

Node *head=NULL;
pthread_mutex_t mutex;
pthread_cond_t cond;

//生产者线程
void *producer(void *arg)
{
    int n=*(int*)arg;
    while(1)
    {
        Node*node=malloc(sizeof(Node));
        if(node==NULL)
            return ;
        node->data=rand()%100;
        node->next=NULL;
        pthread_mutex_lock(&mutex);
        node->next=head;
        head=node;
        printf("P[%d] [%d]\n",n,node->data);
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
        sleep(1);
    }   
}       
//消费者线程
void *consumer(void *arg)
{       
    int n=*(int*)arg;
    while(1)
    {   
        pthread_mutex_lock(&mutex);
        if(head==NULL)
        {
            pthread_cond_wait(&cond,&mutex);
        }
        if(head==NULL)
        {
            pthread_mutex_unlock(&mutex);
            continue;
        }
        printf("C[%d] [%d]\n",n,head->data);
        Node*cur=head;
        head=cur->next;
        pthread_mutex_unlock(&mutex);
        free(cur);
        cur=NULL;
        sleep(1);
    }
}

int main()
{
    srand(time(NULL));
    pthread_mutex_init(&mutex,NULL);//初始化互斥锁
    pthread_cond_init(&cond,NULL);//初始化条件变量

    pthread_t thread1[5];
    pthread_t thread2[5];
    int ret;
    int i=0;
    int arr[5];
    for(i=0;i<5;i++)
    {
        arr[i]=i;
        ret=pthread_create(&thread1[i],NULL,producer,&arr[i]);//创建生产者线程
        if(ret<0)
        {
            printf("pthread_create[%s] error\n",strerror(ret));
            return -1;
        }
    }
    for(i=0;i<5;i++)
    {
        arr[i]=i;
        ret=pthread_create(&thread2[i],NULL,consumer,&arr[i]);//创建消费者线程
        if(ret<0)
        {
            printf("pthread_create[%s] error\n",strerror(ret));
            return -1;
        }
    }
    int j=0;
    for(j=0;j<5;j++)
    {
        pthread_join(thread1[j],NULL);
        pthread_join(thread2[j],NULL);
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}
  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值