linux:线程互斥&线程同步

一、线程互斥

1.互斥

顾名思义,就是相互排斥,比如你去ATM取钱,你在ATM机里取钱的时候,那别人就不能够再取钱,只能够再外面等待着,取钱这件事就是相互排斥的

2.线程互斥是什么?

某一资源同一时刻只能让一个线程使用,假设有多个线程都要访问同一资源,线程互斥就是同一时刻该资源只能被一个线程访问,而其他线程就只能等待

3.为什么要有线程互斥?

1.多个线程(或者多个进程)之间,执行时的先后顺序不确定,何时执行何时切换出CPU的顺序也不确定;
2.多个线程(或者多个进程)在访问同一变量时的动作往往不是原子的

有时候,变量需要在线程之间进行共享,这样的变量称为共享变量,可以通过数据
之间的共享,来完成线程之间的交互,线程之间是共享虚拟地址空间的,但是有时
候因为共享会带来一些问题

比如:火车票的售卖,假设现在只剩3张票了,但是四个人同一时间都在买票,因
为不互斥,故这四个人都可以买到票,就会使票的数目成为-1,但是在现实生活
中这种情况是允许出现的,买票的四个人就是四个线程,票就是线程之间共享的
同一变量,因为买票操作不互斥,就会出现这种情况,多个线程访问同一变量,
就会出现数据错乱

原子

就是不可拆分的,是CPU调度的最小单位

非原子例子:

 int count=0;
 ++count;

这里写图片描述
这里写图片描述

CPU执行命令时,是一条一条的执行的,一定是某条指令要么执行完了,要么执行完了,不会出现执行某条命令执行一半就去执行别的命令,即就是每条命令之间的执行时非原子的,但是在执行某条命令时是原子的,不可被打断的,不可进行拆分

4.线程互斥怎么实现?

这里写图片描述

5.线程互斥的相关函数

(1)初始化互斥量

静态分配:

pthread_mutex_t mutex=PHREAD_MUTEX_INITIALIZER

动态分配:

int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr)
参数说明:
mutex:需要初始化的互斥量(此参数是输出型参数)
       输出型参数:就是说这个参数既可以作为函数的参数,也可以作为函数的返回值
attr:创建的互斥量的属性,一般设为NULL

restrict:是C语言中的关键字
  功能:告诉此函数的调用者,在进行函数调用时,不允许修改此指针的执行,但是可以修改此指针指向空间的值

ps:推荐使用动态分配初始化互斥量,尽量不要使用静态分配

(2)销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t* mutex);

1.静态分配的PTHREAD_MUTEEX_INITIALZER,不需要销毁,由操作系统进行销毁
2.不要销毁一个已经加锁的互斥量
3.已经销毁的互斥量,要确保后面不会再加锁

(3)互斥量加锁

int pthread_mutex_lock(pthread_mutex_t* mutex);

调用此函数会有两种情况:

1.互斥量处于未锁状态,该函数会将此互斥量锁定,同时返回成功
2.此函数要锁定的互斥量已经被其他线程加锁,那此函数就会一直阻塞等待着,直到此互斥量解锁,此函数才会开始执行;

(4)互斥量解锁

int pthread_mutex_unlock(pthread_mutex_t* mutex);

注意:加锁和解锁的操作是原子的

6.示例

(1)不加锁
代码:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int count=10;
void*ThreadEnter(void*arg)
{
    char*ID=(char*)arg;
    while(1)
    {
        if(count>0)
        {
            sleep(1);
            printf("count=%d,ID=%s\n",count,ID);
            count--;
        }
        else
        {
            break;
        }
    }
}
int main()
{
    pthread_t tid1,tid2,tid3;
    pthread_create(&tid1,NULL,ThreadEnter,"thread1");
    pthread_create(&tid2,NULL,ThreadEnter,"thread2");
    pthread_create(&tid3,NULL,ThreadEnter,"thread3");
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);
    return 0;
}

运行结果:
这里写图片描述
由结果看:本应该是count=0就退出循环,可是count却减到-1了,导致了数据的错乱
(2)加锁
代码:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int count=10;
pthread_mutex_t mutex;//声明互斥量
void*ThreadEnter(void*arg)
{
    char*ID=(char*)arg;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(count>0)
        {
            sleep(1);
            printf("count=%d,ID=%s\n",count,ID);
            count--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
}
int main()
{
    pthread_t tid1,tid2,tid3;
    pthread_mutex_init(&mutex,NULL);//初始化互斥量
    pthread_create(&tid1,NULL,ThreadEnter,"thread1");
    pthread_create(&tid2,NULL,ThreadEnter,"thread2");
    pthread_create(&tid3,NULL,ThreadEnter,"thread3");
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_join(tid3,NULL);
    pthread_mutex_destroy(&mutex);//销毁互斥量
    return 0;
}

运行结果:
这里写图片描述

加锁之后,临界区的代码在同一时刻只能由一个线程执行,其他线程只能等待

二、线程同步

1.为什么要使用条件变量

假设我们在ATM里取钱,但是突然发现ATM中没钱了,现在只能在外面等着,因为有可能会有人给里面存钱哦,有两种等待场景:

第一种:一直在外面等着,但是在等待期间,你会时不时的进ATM中查看,机子里有没有钱
第二种:你可以在外面等待的人中找个人,如果ATM中有钱了,让她给你说一下,在此期间,你可以做你自己的事

从这两种情况来看,我们当然首选第二种,因为效率高啊,但是第二种情况的实现需要一些条件,那就是有人给你说,有钱了,如果没有人给你说的话,你就只能自己在哪里等着了

2.条件变量的相关函数

(1)初始化函数

int pthread_cond_init(pthread_cond_t*restrict  cond, const pthread_condattr_t* restrict attr)
参数说明:
   cond:需要初始化的条件变量 
   attr:初始化的条件变量的属性,一般设为NULL

(2)销毁条件变量

int pthread_cond_destroy(pthread_cond_t*  cond);

(3)等待条件满足

int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutex_t *restrict mutex);

参数说明:
   cond:要在这个条件变量上等待
   mutex:

(4)唤醒等待

广播式的唤醒等待:

int pthread_cond_broadcast(pthread_cond_t* cond);

此等待可能会给操作系统带来危害,如果有很多的线程在等待,如果广播式的唤醒,所有的线程都想执行自己的代码,就会造成堵塞

一对一式的唤醒等待

int pthread_cond_signal(pthread_cond_t *cond);

3.示例

1.

(1)不加条件变量
代码

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void*ThreadEnter1(void*arg)
{
    (void)arg;
    while(1)
    {
        printf("投篮\n");
        sleep(1);
    }
}
void*ThreadEnter2(void*arg)
{
    (void)arg;
    while(1)
    {
    printf("传球\n");
    sleep(3);
    }
}

int main()
{
    pthread_t t1,t2;
    pthread_create(&t1,NULL,ThreadEnter1,NULL);
    pthread_create(&t2,NULL,ThreadEnter2,NULL);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    return 0;
}

运行结果:
这里写图片描述
结果解析:本来应该是一次传球,一次投篮,但是现在结果却是乱序的
(2)加了条件变量
代码

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void*ThreadEnter1(void*arg)
{
    (void)arg;
    while(1)
    {
        pthread_cond_wait(&cond,&mutex);
        printf("投篮\n");
        sleep(1);
    }
}
void*ThreadEnter2(void*arg)
{
    (void)arg;
    while(1)
    {
        printf("传球\n");
        sleep(3);
        pthread_cond_signal(&cond);
    }
}

int main()
{
    pthread_t t1,t2;
    pthread_create(&t1,NULL,ThreadEnter1,NULL);
    pthread_create(&t2,NULL,ThreadEnter2,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果:
这里写图片描述
结果解析:
第一次的传球是因为先执行了一次传球,然后此线程等待3秒钟,在这3秒钟中,此线程又被操作系统调度了一次,所以又输出了一次传球,从此之后,传球和投篮就同步了

猜你喜欢

转载自blog.csdn.net/dangzhangjing97/article/details/79947187