Linux 线程 (第二阶段 互斥锁)

4.线程同步之互斥量加锁解锁

学习线程可参考的博文:https://www.cnblogs.com/xiehongfeng100/p/4620852.html
与互斥锁相关API

  • 互斥量(mutex)从本质上来说是一把锁,在访问共享资源(被加锁解锁之间包含的就是共享资源,并不是线程之间和进程共享的内存)前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
    1. 创建及销毁互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);//第一个参数是取锁地址,第二个参数是锁的属性(NULL);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 返回:若成功返回0,否则返回错误编号

要用默认的属性初始化互斥量,只需把attr设置为NULL

  1. 加锁及解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 返回:若成功返回0,否则返回错误编号

以下代码(demo5.c)是创建3个线程,对线程内容加锁,每次只有一个线程可以向前运行,然后才会轮到其他的线程竞争运行。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);C
int g_data = 0;

pthread_mutex_t mutex;

void *func1(void *arg)
{
        int i;
        pthread_mutex_lock(&mutex);//上锁

        for(i=0;i<5;i++){
                printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
                printf("t1:param is %d\n",*((int *)arg));// 被锁包含的就是共享资源
                sleep(1);
        }
        pthread_mutex_unlock(&mutex);//解锁
}

void *func2(void *arg)
{
        pthread_mutex_lock(&mutex);//上锁

        printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t2:param is %d\n",*((int *)arg));// 被锁包含的就是共享资源

        pthread_mutex_unlock(&mutex);//解锁
}

void *func3(void *arg)
{
        pthread_mutex_lock(&mutex);//上锁

        printf("t3:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t3:param is %d\n",*((int *)arg));// 被锁包含的就是共享资源

        pthread_mutex_unlock(&mutex);//解锁
}
int main()
{
        int ret;
        int param = 100;
        pthread_t t1;
        pthread_t t2;
        pthread_t t3;

        pthread_mutex_init(&mutex,NULL);//创建锁(初始化锁)

        ret = pthread_create(&t1,NULL,func1,(void *)&param);//创建t1线程
        if(ret == 0){
                printf("main:create t1 success\n");
        }

        ret = pthread_create(&t2,NULL,func2,(void *)&param);//创建t2线程2
        if(ret == 0){
                printf("main:create t2 success\n");
        }

        ret = pthread_create(&t3,NULL,func3,(void *)&param);//创建t3线程
        if(ret == 0){
                printf("main:create t3 success\n");
        }

        printf("main:%ld\n",(unsigned long)pthread_self());

        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
        pthread_join(t3,NULL);

        pthread_mutex_destroy(&mutex);//销毁锁
        return 0;
}

从运行结果图可以看出,被互斥量锁上的程序需要跑完才会到另一个有加互斥锁的线程,如图main线程没有加锁,所以会在无规律的出现;任何其他试图再次对互斥量加锁的线程(t2、t3)将会被阻塞直到当前线程(t1)释放该互斥锁.
在这里插入图片描述

5.互斥锁限制共享资源的访问

这里代码(demo6.c)真正对共享资源加锁解锁的操作,前面的只是让我们知道线程被加锁后不会被其他加锁的线程打断,共享资源只是简单printf一些信息,并没有对数据(比如g_data==3如何让其一定在线程t1然后操作线程退出)加锁进行逻辑控制。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int g_data = 0;

pthread_mutex_t mutex;

void *func1(void *arg)
{
        printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t1:param is %d\n",*((int *)arg));
        pthread_mutex_lock(&mutex);//加锁
        while(1){

                printf("t1: %d\n",g_data++);
                sleep(1);
                if(g_data == 3){
                        pthread_mutex_unlock(&mutex);//解锁
                        printf("t1 quit===========================\n");
                    	pthread_exit(NULL);
                       //  exit(0);//要注意的是线程这里退出就破坏了进程的内存空间,所以整个进程就退出了
                }
        }
}

void *func2(void *arg)
{
        printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t2:param is %d\n",*((int *)arg));
        while(1){

                printf("t2: %d\n",g_data);
                pthread_mutex_lock(&mutex);//加锁
                g_data++;
                pthread_mutex_unlock(&mutex);//解锁
                sleep(1);
        }
}
int main()
{
        int ret;
        int param = 100;
        pthread_t t1;
        pthread_t t2;

        pthread_mutex_init(&mutex,NULL);//创建锁

        ret = pthread_create(&t1,NULL,func1,(void *)&param);//创建线程
        if(ret == 0){
                printf("main:create t1 success\n");
        }

        ret = pthread_create(&t2,NULL,func2,(void *)&param);//创建线程
        if(ret == 0){
                printf("main:create t2 success\n");
        }

        printf("main:%ld\n",(unsigned long)pthread_self());
        while(1){

                printf("main:%d\n",g_data);
                sleep(1);
        }

        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
        pthread_mutex_destroy(&mutex);//销毁锁

        return 0;
}

此处代码线程的逻辑是这样的,互斥锁锁的位置不一样,共享资源就受到的限制不一样,比如这个程序两个情况:线程t1和t2创建成功后,第1种情况是:线程t1先运行到while1前加锁动作,这个时候线程t2也运行了,t2就打印了加锁动作的前面3句话,没锁就阻塞这,等下一轮循环的时候t2线程因为没锁阻塞着,直到线程t1的g_data3,这个锁才会被打开,线程t2才会有加锁的动作,data++字段才会运行; 所以要是让t1先做加锁动作的话,t2只能等到t1的g_data3,解锁了才线程t2才可以可以拿到锁,然后继续运行。
第2种情况就是线程2先运行到加锁动作(这个时候t1就打印加锁动作的前面两句话),t2自加1后解锁,然后睡眠1s😁;加锁动作被线程t1执行了,线程t1运行while语句,线程t2没有锁阻塞着,然后就是与第一种情况一样了,直到线程t1的g_data==3后退出线程,锁打开,线程t2才拿到锁然后继续运行。
运行结果如下图:
在这里插入图片描述

写测试脚本步骤:
1、先在demo6.c里把func1的线程退出改成进程退出
在这里插入图片描述
2、创建脚本(test.sh)几次运行就写几个./进程名(这里是a.out)

./a.out
./a.out
./a.out//运行3次代码

3、修改这个(test.sh)脚本的权限指令:chmod +x test.sh
4、直接运行./test.h
运行结果图如图:运行两次的demo6.c
在这里插入图片描述

另一种的写法测试demo6.c的程序(调用进程的函数用system):
1、直接写一个test.c文件

#include <stdlib.h>

int main()
{
        int i = 0;
        for(i=0; i<100;i++){
                system("./thread");
        }
}

2、编译直接运行,发现ctrl +c杀不掉这个进程,因为我们停止的是demo6.c编译的程序,并不是这个test.c编译后的a.out的进程,所以需要用kill 9 pid(a.out)的方式结束。如下图:
在这里插入图片描述

6.什么情况造成死锁

互斥锁进入死锁情况(就是互斥锁的引用不当导致多个线程代码无法继续进行,就是两个竞争锁的线程卡住了,程序就停止不前了)死锁情况就是有两个锁或以上的锁才会造成死锁这种情况:代码(demo7.c)如下

//此处我们拿锁和加锁可以理解成一个意思
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);C
int g_data = 0;

pthread_mutex_t mutex;
pthread_mutex_t mutex2;

void *func1(void *arg)//线程t1
{
        int i;
        pthread_mutex_lock(&mutex);//先拿到锁1
        sleep(1);
        pthread_mutex_lock(&mutex2);//想拿到锁2(可惜被线程2拿走了)

        for(i=0;i<5;i++){
                printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
                printf("t1:param is %d\n",*((int *)arg));
                sleep(1);
        }
        pthread_mutex_unlock(&mutex);
}

void *func2(void *arg)//线程t2
{
        pthread_mutex_lock(&mutex2);//先拿到锁2
        sleep(1);
        pthread_mutex_lock(&mutex);//想拿到锁1(可惜被线程t1拿了)

        printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t2:param is %d\n",*((int *)arg));

        pthread_mutex_unlock(&mutex);
}

void *func3(void *arg)//线程t3
{
        pthread_mutex_lock(&mutex);//拿锁1

        printf("t3:%ld thread is create\n",(unsigned long)pthread_self());
        printf("t3:param is %d\n",*((int *)arg));

        pthread_mutex_unlock(&mutex);
}

int main()
{
        int ret;
        int param = 100;
        pthread_t t1;
        pthread_t t2;
        pthread_t t3;

        pthread_mutex_init(&mutex,NULL);//创建锁
        pthread_mutex_init(&mutex2,NULL);

        ret = pthread_create(&t1,NULL,func1,(void *)&param);//创建线程t1
        if(ret == 0){
                printf("main:create t1 success\n");
        }

        ret = pthread_create(&t2,NULL,func2,(void *)&param);//创建线程t2
        if(ret == 0){
                printf("main:create t2 success\n");
        }

        ret = pthread_create(&t3,NULL,func3,(void *)&param);//创建线程t3
        if(ret == 0){
                printf("main:create t3 success\n");
        }

        printf("main:%ld\n",(unsigned long)pthread_self());

        pthread_join(t1,NULL);
        pthread_join(t2,NULL);//等待线程运行
        pthread_join(t3,NULL);

        pthread_mutex_destroy(&mutex);//销毁锁
       	pthread_mutex_destroy(&mutex2);

        return 0;
}


以上代码只是死锁的一种现象:线程t1拿到锁1然后休眠1s,同时(或者稍快稍慢于线程t1运行)线程t2拿到锁2,然后休眠1s时;线程t1休眠1s要拿锁2,可是被线程t2拿走了,线程t1阻塞;同样的线程t2想拿锁1,被线程t1拿了,没锁阻塞在这,这就造成了死锁现象。(线程t3如果先于线程t1运行,那么线程t3会打印出其线程的信息,如果线程t1先拿到锁,那么线程t3就一直阻塞住了。)简单的说就是线程t1、t2都想拿到对方先拿到的锁,都没有进行解自己先拿到的锁,导致线程的死锁。
运行结果如下图:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值