线程的同步与互斥

线程的同步与互斥:

  • 互斥:当一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。如:当线程A在使用打印机时,其他线程都需要等待。
  • 同步:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。如:A任务的运行依赖B任务产生的数据。
  • 同步保证了线程运行的顺序性,互斥保证了线程运行的安全性。
  • 同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
  • 互斥具有唯一性和排它性,访问是无序的。
  • 由于线程共享进程的资源和地址空间,所以在访问到他们的公共资源的时候,一定会出现线程的同步和互斥现象,多线程访问一个数据的次序一定是杂乱无章的,所以我们引入互斥锁保证在某一端时间内只有一个线程在执行某些代码,条件变量完成同步过程。
  • 实现线程同步互斥的四种方式
    1.临界区:适合一个进程内的多线程访问公共区域或代码段使用
    2.互斥量:适合不同进程内多线程访问公共区域或代码段使用,与临界区相似
    3.事件:通过线程间触发事件实现同步互斥
    4.信号量:与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中的PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减1,直到资源数小于等于0。
    这里写图片描述
    随着现在科技的不断发展,许多事情都可以在网上进行,如在网上购票。
  • 现在就举一个看电影买票的例子,一个影厅的座位数量是固定的,比如一个影厅有100个座位,那最多也只能卖出去100张票,不能存在卖出101张甚至更多的情况,如果这样那估计这个影院马上就要被客户投诉了。可是每个人买票可能是在同一时间使用不同的手机和不同软件,所以类比操作系统中多线程在运行。

下面看一段代码:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int ticket = 100;

void *route(void *arg)
{
    char *id = (char*)arg;
    while(1)
    {
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket : %d\n",id,ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}
int main()
{
    pthread_t t1,t2,t3,t4;

    pthread_create(&t1,NULL,route,"thread 1");
    pthread_create(&t2,NULL,route,"thread 2");
    pthread_create(&t3,NULL,route,"thread 3");
    pthread_create(&t4,NULL,route,"thread 4");

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

看着貌似没什么问题,但是请看运行结果:
这里写图片描述

  • 前面的结果都是正常的,但为什么会出现-1,-2呢??
    原因是因为if语句判断条件为真以后,代码可以并发的切换到其他线程,而usleep这个模拟漫长业务的过程,在这个漫长的业务过程中,可能会有很多个线程会进入该代码段
  • 如果想要解决上面的问题也很简单,做到以下四点就可以了:
    1.代码必须要有互斥行为,当代吗进入临界区执行时,不允许其他线程进入临界区
    2.如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只允许一个线程进入该临界区
    3.如果线程不在临界区执行,那么该线程不能阻塞其他线程进入临界区
    4.全局变量被多个线程同时访问,要保证原子的
  • 这就要需要对互斥量进行加锁和解锁操作:
    下面介绍互斥量的接口
  • 互斥量的初始化
    法1:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

法2:动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
  • 互斥量的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 销毁互斥量需要注意:
    1.使用PTHREAD_MUTEX_INITALIZER初始化的互斥量不需要销毁
    2.不要销毁一个已经加锁的互斥量
    3.已经销毁的互斥量,要确保后面不会有线程再尝试加锁

  • 互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);解锁

改进上面的代码:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sched.h>
int ticket = 100;
pthread_mutex_t mutex;

void *route(void *arg)
{
    char *id = (char*)arg;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket : %d\n",id,ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
}
int main()
{
    pthread_t t1,t2,t3,t4;

    pthread_mutex_init(&mutex,NULL);
    pthread_create(&t1,NULL,route,"thread 1");
    pthread_create(&t2,NULL,route,"thread 2");
    pthread_create(&t3,NULL,route,"thread 3");
    pthread_create(&t4,NULL,route,"thread 4");

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

可以看到运行结果和我们预想的一样:
这里写图片描述

为了完成同步过程,我们引入条件变量:

  • 条件变量的初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
  • 条件变量的销毁
int pthread_cond_destroy(pthread_cond_t *cond)
  • 等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量


1.必须有互斥锁与它配合使用,这是为什么呢??
A.条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,永远等下去也不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并通知等待在条件变量上的线程
B.条件不会无缘无故的突然就满足了,必然会牵扯到共享数据的变化,所以要用互斥锁来保护,没有互斥锁,就无法安全的获取和修改共享数据
2.等的同时释放锁,如果不释放就会造成死锁问题,当signal或其它方式唤醒时要重新获得锁并从等待处继续执行

  • 条件变量的使用规范
  • 等待条件
pthread_mutex_lock(&mutex);
while(条件为假)
pthread_cond_wait(cond,mutex);
  • 修改条件
pthread_mutex_unlock(&mutex);
给条件发送信号
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
  • 唤醒等待
int pthread_cond_broadcast(pthread_cont_t *cond);唤醒一群
int pthread_cond_signal(pthread_cont_t *cond);唤醒单个

下面是一个简单的程序:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;

void *r1(void *arg)
{
    while(1)
    {
        pthread_cond_wait(&cond,&mutex);
        printf("hello world\n");
    }
}
void *r2(void *arg)
{
    while(1)
    {
        pthread_cond_signal(&cond);
        sleep(1);
    }
}
int main()
{
    pthread_t t1,t2;

    pthread_cond_init(&cond,NULL);
    pthread_mutex_init(&mutex,NULL);

    pthread_create(&t1,NULL,r1,NULL);
    pthread_create(&t2,NULL,r2,NULL);

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

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

每隔1秒就会输出一个hello world:
这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章