目录

0. 互斥锁与信号量

同步互斥概述

1. 互斥锁

1.1 互斥锁的概念

1.2 互斥锁初始化:pthread_mutex_init函数

1.3 互斥锁上锁:pthread_mutex_lock函数

1.4 互斥锁解锁:pthread_mutex_unlock函数

1.5 销毁互斥锁:pthread_mutex_destroy函数

1.6 互斥锁案例

1.61 不使用互斥锁的结果

 1.62 使用互斥锁的结果 

2. 信号量

2.1 信号量的概念

2.2 信号量的初始化:sem_init函数

2.3 信号量的P操作:sem_wait函数

2.4 信号量的V操作:sem_post函数

2.5 获取信号量的计数值:sem_getvalue函数

2.6 信号量的销毁:sem_destroy函数

2.7 信号量的使用

2.7.1 信号量实现互斥功能

2.7.2 信号量实现同步功能

 总结:


0. 互斥锁与信号量

同步互斥概述

        在多任务操作系统中,同时运行的多个任务可能都需要访问/使用同一种资源。多个任务之间有依赖关系,某个任务的运行依赖于另一个任务。

        同步和互斥就是用于解决这两个问题的。

互斥:

        一个公工资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。POSIX标准中进程和线程同步和互斥的方法,主要有信号量和互斥锁两种方式。

同步:

        两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。

        同步就是在互斥的基础上有顺序。

1. 互斥锁

1.1 互斥锁的概念

        mutex是一种简单的加锁的方法来控制对共享资源的访问,mutex只有两种状态,即上锁(lock)和解锁(unlock)。在访问该资源前,首先应申请mutex。

  •         如果mutex处于unlock状态,则会申请到mutex并立即lock。
  •         如果mutex处于lock状态,则默认阻塞申请者。unlock操作应该由lock者进行。

初始化互斥锁:pthread_mutex_init函数。

        mutex用pthread_mutex_t数据类型表示,在使用互斥锁前,必须先对它进行初始化。

静态分配的互斥锁:

        pthread_mutex_t mutex =   PTHREAD_MUTEX_INITIALIZER;

动态分配互斥锁:

        pthread_mutex_t mutex;

销毁互斥锁:pthread_mutex_destroy

1.2 互斥锁初始化:pthread_mutex_init函数

pthread_mutex_init函数:

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

功能:

        初始化一个互斥锁。

参数:

        mutext:互斥锁的地址。

        attr:互斥锁的属性,NULL为默认的属性。

返回值:

        成功:0

        失败:非0

1.3 互斥锁上锁:pthread_mutex_lock函数

pthread_mutex_lock函数

#include<pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:

        对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁。

参数:

        mutex:指定的互斥锁。

返回值:

        成功:0

        失败:非0

#include<pthread.h>

int pthread_mutex_trylock(pthread_mutex_t * mutex);

功能:

        对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非0 

1.4 互斥锁解锁:pthread_mutex_unlock函数

pthread_mutex_unlock函数

#include<pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:

        对指定的互斥锁解锁。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非0

1.5 销毁互斥锁:pthread_mutex_destroy函数

pthread_mutex_destroy函数

#include<pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:

        销毁指定的一个互斥锁。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非0

1.6 互斥锁案例

1.61 不使用互斥锁的结果
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int money = 10000;

void* pthread_fun1(void* arg)
{
    int get, yu, shiji;
    get = 10000;

    printf("zhangsan look balance\n");
    sleep(1);
    yu = money;

    printf("zhangsan spent money\n");
    sleep(1);
    if (get > yu)
    {
        shiji = 0;

    }
    else
    {
        shiji = get;
        yu = yu - get;
        money = yu;
    }
    printf("zhangsan want spent %d, fact spent is %d, balance is %d\n", get, shiji, yu);
    pthread_exit(NULL);
}
void* pthread_fun2(void* arg)
{
    int get, yu, shiji;
    get = 10000;

    printf("lisi look balance\n");
    sleep(1);
    yu = money;

    printf("lisi spent money\n");
    sleep(1);
    if (get > yu)
    {
        shiji = 0;

    }
    else
    {
        shiji = get;
        yu = yu - get;
        money = yu;
    }
    printf("lisi want spent %d, fact spent is %d, balance is %d\n", get, shiji, yu);
    pthread_exit(NULL);
}
int main()
{

    pthread_t thread1, thread2;

    if (pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
    {
        perror("fail to pthread_create");
        exit(1);
    }
    if (pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
    {
        perror("fail to pthread_creat");
        exit(1);
    }

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    return 0;
}

执行截图:

 1.62 使用互斥锁的结果 

代码示例:

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

int money = 10000;
//第一步:创建互斥锁
pthread_mutex_t mutex;

void* pthread_fun1(void* arg)
{
    int get, yu, shiji;
    get = 10000;
    //第三步,互斥锁上锁
    pthread_mutex_lock(&mutex);
    printf("zhangsan look balance\n");
    sleep(1);
    yu = money;

    printf("zhangsan spent money\n");
    sleep(1);
    if (get > yu)
    {
        shiji = 0;

    }
    else
    {
        shiji = get;
        yu = yu - get;
        money = yu;
    }
    printf("zhangsan want spent %d, fact spent is %d, balance is %d\n", get, shiji, yu);
    //第四步,互斥锁解锁
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}
void* pthread_fun2(void* arg)
{
    int get, yu, shiji;
    get = 10000;
    //第三步,互斥锁上锁
    pthread_mutex_lock(&mutex);
    printf("lisi look balance\n");
    sleep(1);
    yu = money;

    printf("lisi spent money\n");
    sleep(1);
    if (get > yu)
    {
        shiji = 0;

    }
    else
    {
        shiji = get;
        yu = yu - get;
        money = yu;
    }
    printf("lisi want spent %d, fact spent is %d, balance is %d\n", get, shiji, yu);
    //第四步,互斥锁解锁
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}
int main()
{
    //第二步,初始化互斥锁
    pthread_mutex_init(&mutex, NULL);
    pthread_t thread1, thread2;

    if (pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
    {
        perror("fail to pthread_create");
        exit(1);
    }
    if (pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
    {
        perror("fail to pthread_creat");
        exit(1);
    }

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    //第五步,销毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

执行结果:

2. 信号量

2.1 信号量的概念

        信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。

        编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞。

        信号量又称之为PV操作,PV是对信号量的操作,一次P操作使信号量sem减1,一次V操作使信号量sem+1。对于P操作,如果信号量的sem值为小于等于0,则P操作就会阻塞,如果信号量的值大于0,才可以执行P操作进行减1。

信号量主要用于进程或线程间的同步和互斥这两种典型情况。

  • 若用于互斥,几个进程(或线程)往往只设置一个信号量。
  • 若用于同步操作,往往会设置多个信号量,并且安排不同的初始值,来实现它们之间的执行顺序。

信号量用于互斥:

信号量用于同步:

 

2.2 信号量的初始化:sem_init函数

sem_init函数

#include<semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:

        创建一个信号量并初始化它的值。

参数:

        sem:信号量的地址。

        pshared:等于0,信号量在线程间共享;不等于0,信号量在进程间共享。

        value:信号量的初始值。

返回值:

        成功:0

        失败:-1

2.3 信号量的P操作:sem_wait函数

sem_wait函数

#include<semaphore.h>

int sem_wait(sem_t *sem);

功能:

        将信号量的值减1,若信号量的值小于等于0,此函数会引起调用者阻塞。

参数:

        sem:信号量的地址。

返回值:

        成功:0

        失败:-1

#include<semaphore.h>

int sem_trywait(sem_t *sem);

功能:

        将信号量的值减1,若信号量的值小于0,则对信号量的操作失败,函数立即返回。

参数:

        sem:信号量地址。

返回值:

        成功:0

        失败:-1

2.4 信号量的V操作:sem_post函数

sem_post函数

#include<semaphore.h>

int sem_post(sem_t *sem);

功能:

        将信号量的值加1并发出信号唤醒等待线程。

参数:

        sem:信号量地址。

返回值:

        成功:0

        失败:-1 

2.5 获取信号量的计数值:sem_getvalue函数

sem_getvalue函数

#include<semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);

功能:

        获取sem标识的信号量的值,保存在sval中。

参数:

        sem:信号量地址。

        sval:保存信号量值的地址。

返回值:

        成功:0

        失败:-1

2.6 信号量的销毁:sem_destroy函数

sem_destroy函数

#include<semaphore.h>

int sem_destroy(sem_t *sem);

功能:        

        删除sem标识的信号量。

参数:

        sem:信号量地址。

返回值:

        成功:0

        失败:-1

2.7 信号量的使用

2.7.1 信号量实现互斥功能

代码示例:

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

//第一步,创建信号量
sem_t sem;

void printer(char* str)
{
    //第三步,P操作
    sem_wait(&sem);
    while (*str)
    {
        putchar(*str);
        fflush(stdout);
        str++;
        sleep(1);
    }
    //第四步,V操作
    sem_post(&sem);
}

void* thread_fun1(void* arg)
{
    char* str1 = "hello";
    printer(str1);
}
void* thread_fun2(void* arg)
{
    char* str2 = "world";
    printer(str2);
}

int main()
{
    //第二步,初始化信号量
    sem_init(&sem, 0, 1);
    pthread_t tid1, tid2;

    pthread_create(&tid1, NULL, thread_fun1, NULL);
    pthread_create(&tid2, NULL, thread_fun2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("\n");
    //第五步,销毁信号量
    sem_destroy(&sem);
    return 0;
}

执行截图:

2.7.2 信号量实现同步功能

代码示例:

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>

char ch = 'a';
//第一步,创建两个信号量
sem_t sem_g, sem_p;

void* pthread_g(void* arg)
{
    while (ch <= 'z')
    {
        //第四步,后执行线程
        sem_wait(&sem_g);
        ch++;
        sleep(1);
        //第六步,V操作
        sem_post(&sem_p);
    }
}

void* pthread_p(void* arg)
{
    while (ch < 'z')
    {
        //第三步,先执行线程
        sem_wait(&sem_p);
        printf("%c", ch);
        fflush(stdout);
        //第五步,V操作
        sem_post(&sem_g);
    }
}
int main()
{
    //第二步,初始化信号量
    sem_init(&sem_g, 0, 0);
    sem_init(&sem_p, 0, 1);
    pthread_t tid1, tid2;

    pthread_create(&tid1, NULL, pthread_g, NULL);
    pthread_create(&tid2, NULL, pthread_p, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("\n");
    //第七步,销毁信号量
    sem_destroy(&sem_g);
    sem_destroy(&sem_p);
    return 0;
}

执行截图:

 总结:

        总的来说,互斥锁和信号量都是线程和进程同步的重要工具。互斥锁主要用于保护资源,保证同一时间只有一个线程或进程访问某一资源,从而避免并发问题。而信号量更多的是用于线程和进程之间的通信和同步,控制在一定范围内的并发访问,为程序提供更细粒度的并发控制。

        掌握互斥锁和信号量的使用和区别,能极大地提高多线程和多进程编程的效率和稳定性。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐