linux:线程的控制 互斥、同步、死锁

一、线程的控制

线程控制中的互斥(Mutual Exclusion)和同步(Synchronization)是并发编程中非常重要的概念,用于解决多线程环境下对共享资源的访问冲突和数据一致性问题。

互斥(Mutual Exclusion)

互斥是指在任何时候,只允许一个线程访问某个特定的资源或代码段(临界区)。这是通过互斥锁(Mutex)来实现的。当一个线程进入临界区时,它会锁定这个区域,阻止其他线程进入,直到它退出临界区并释放锁。

互斥锁的主要特点

  • 互斥性:确保同时只有一个线程可以进入临界区。
  • 无死锁:如果系统没有死锁的先决条件,那么互斥锁也不会导致死锁。
  • 避免饥饿:互斥锁的实现应该避免任何线程无限期地等待访问资源。

同步(Synchronization)

同步是指多个线程在执行过程中,按照一定的顺序或规则来访问共享资源或执行特定的操作。同步用于协调线程之间的活动,以保证数据的完整性和一致性。

同步机制包括

  • 信号量(Semaphores):一种允许多个线程同时访问某个共享资源的同步机制。信号量有一个计数器,表示当前可用资源的数量。
  • 条件变量(Condition Variables):与互斥锁一起使用,允许线程在某个条件为真之前挂起。当条件变为真时,等待的线程会被唤醒。
  • 屏障(Barriers):允许多个线程在某个点同步,即所有线程都必须到达这个点后才能继续执行。
  • 事件(Events):用于线程间的通信,一个线程可以通知另一个或多个线程某个事件已经发生。

二、*互斥锁步骤

互斥 ===》在多线程中对临界资源的排他性访问。

互斥机制 ===》互斥锁 ===》保证临界资源的访问控制。

pthread_mutex_t mutex;

互斥锁类型 互斥锁变量 内核对象

框架:

定义互斥锁 ==》初始化锁 ==》加锁 ==》解锁 ==》销毁

**** *** ***

1、定义:

pthread_mutex_t mutex;

2、初始化锁

cs 复制代码
 int pthread_mutex_init(
            pthread_mutex_t *mutex,
            const pthread_mutexattr_t *attr);

功能:将已经定义好的互斥锁初始化。

参数:mutex 要初始化的互斥锁

atrr 初始化的值,一般是NULL表示默认锁

返回值:成功 0

失败 非零

3、加锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:用指定的互斥锁开始加锁代码

加锁后的代码到解锁部分的代码属于原子操作,

在加锁期间其他进程/线程都不能操作该部分代码

如果该函数在执行的时候,mutex已经被其他部分

使用则代码阻塞。

参数: mutex 用来给代码加锁的互斥锁

返回值:成功 0

失败 非零

4、解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:将指定的互斥锁解锁。

解锁之后代码不再排他访问,一般加锁解锁同时出现。

参数:用来解锁的互斥锁

返回值:成功 0

失败 非零

5、销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:使用互斥锁完毕后需要销毁互斥锁

参数:mutex 要销毁的互斥锁

返回值:成功 0

失败 非零

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int A = 0 ;
pthread_mutex_t mutex;
void * th(void* arg)
{
    
    int i = 5000;
    while(i--)
    {
        pthread_mutex_lock(&mutex);
        int tmp = A;
        printf("A is %d\n",tmp+1);
        A = tmp+1;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_mutex_init(&mutex,NULL);
    pthread_create(&tid1,NULL,th,NULL);
    pthread_create(&tid2,NULL,th,NULL);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}

如果不加锁的话,在 th 函数中,两个线程都在读取和修改全局变量 A。由于两个线程可能几乎同时访问 A,它们之间的操作可能会交错进行,导致不确定的输出结果。

练习:模拟有三个窗口、为10个人服务。

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int WIN =3;
pthread_mutex_t mutex;
void* th(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(WIN>0)
        {
            WIN--;
            pthread_mutex_unlock(&mutex);
            printf("get win\n");
            sleep(rand()%5);    //每个窗口结束的时间不一样
            printf("relese win\n");
            pthread_mutex_lock(&mutex);
            WIN++;
            pthread_mutex_unlock(&mutex);
            break;
        }
        else 
        {
            pthread_mutex_unlock(&mutex);

        }
    }
    return NULL;
}
int main(int argc, char *argv[])
{
    int i = 0 ;
    pthread_mutex_init(&mutex,NULL);
    pthread_t tid[10]={0};
    for(i=0;i<10;i++)
    {
        pthread_create(&tid[i],NULL,th,NULL);
    }
    for(i=0;i<10;i++)
    {
        pthread_join(tid[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

6、trylock

int pthread_mutex_trylock(pthread_mutex_t *mutex);

功能:类似加锁函数效果,唯一区别就是不阻塞。

参数:mutex 用来加锁的互斥锁

返回值:成功 0

失败 非零

E_AGAIN

三、*线程的同步

同步 ===》有一定先后顺序的对资源的排他性访问。

原因:互斥锁可以控制排他访问但没有次序。

linux下的线程同步 ===》信号量机制 ===》semaphore.h posix

sem_open();

信号量的分类:

1、无名信号量 ==》线程间通信

2、有名信号量 ==》进程间通信

框架:

信号量的定义 ===》信号量的初始化 ==》信号量的PV操作 ===》信号量的销毁。

1、信号量的定义 :

sem_t sem;

信号量的类型 信号量的变量

2、信号量的初始化:

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

功能:将已经定义好的信号量赋值。

参数:sem 要初始化的信号量

pshared = 0 ;表示线程间使用信号量

!=0 ;表示进程间使用信号量

value 信号量的初始值,一般无名信号量

都是二值信号量,0 1

0 表示红灯,进程暂停阻塞

1 表示绿灯,进程可以通过执行

返回值:成功 0

失败 -1;

3、信号量的PV 操作

P ===》申请资源===》申请一个二值信号量

V ===》释放资源===》释放一个二值信号量

P操作对应函数 ==》sem_wait();
V操作对应函数 ==》sem_post();

int sem_wait(sem_t *sem);

功能:判断当前sem信号量是否有资源可用。

如果sem有资源(==1),则申请该资源,程序继续运行

如果sem没有资源(==0),则线程阻塞等待,一旦有资源

则自动申请资源并继续运行程序。

注意:sem 申请资源后会自动执行 sem = sem - 1;

参数:sem 要判断的信号量资源

返回值:成功 0

失败 -1
int sem_post(sem_t *sem);

功能:函数可以将指定的sem信号量资源释放

并默认执行,sem = sem+1;

线程在该函数上不会阻塞。

参数:sem 要释放资源的信号量

返回值:成功 0

失败 -1;

4、信号量的销毁

int sem_destroy(sem_t *sem);

功能:使用完毕将指定的信号量销毁

参数:sem要销毁的信号量

返回值:成功 0

失败 -1;

用法:2个线程依次打印出10次hello world

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_H,sem_W;      //定义信号量  sem_t类型
void* th1(void* arg)
{
    int i  =10;
    while(i--)
    {
        sem_wait(&sem_H);
        printf("hello ");  //没有'\n'进入缓冲区,需要用fflush函数刷新
        fflush(stdout);    //清空(刷新)标准输出缓冲区(stdout),并输出数据
        sem_post(&sem_W);  //把信号量传给第二个线程
    }
    return NULL;
}

void* th2(void* arg)
{
    int i  =10;
    while(i--)
    {
        sem_wait(&sem_W); 
        printf("world\n");
        sleep(1);
        sem_post(&sem_H);   //再把信号量传给第一个线程
    }
    return NULL;        
}
int main(int argc, char *argv[])
{
    
    pthread_t tid1,tid2;
    sem_init(&sem_H,0,1);    //第二个参数0表示线程,非0表示进程
    sem_init(&sem_W,0,0);    //第三个参数0表示阻塞等待
    pthread_create(&tid1,NULL,th1,NULL);
    pthread_create(&tid2,NULL,th2,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    sem_destroy(&sem_H);
    sem_destroy(&sem_W);

    return 0;
}

5、计数信号量

对互斥锁的练习,用计数信号量完成

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//int WIN =3;
//pthread_mutex_t mutex;
sem_t sem_WIN;
void* th(void* arg)
{
    sem_wait(&sem_WIN);
    printf("get win\n");
    sleep(rand()%5);
    printf("relese win\n");
    sem_post(&sem_WIN);
    return NULL;
}
int main(int argc, char *argv[])
{
    int i = 0 ;
 //   pthread_mutex_init(&mutex,NULL);
    pthread_t tid[10]={0};
    sem_init(&sem_WIN,0,3);
    for(i=0;i<10;i++)
    {
        pthread_create(&tid[i],NULL,th,NULL);
    }
    for(i=0;i<10;i++)
    {
        pthread_join(tid[i],NULL);
    }
   // pthread_mutex_destroy(&mutex);
   sem_destroy(&sem_WIN);
    return 0;
}

分析:sem_WIN设置为3,则用户1用户2用户3的信号量为 1 、2、3

使用 sem_wait(&sem_WIN); 等待信号量。如果信号量的值大于0,则将其减1并继续执行;如果信号量的值为0,则线程阻塞,直到信号量的值变为大于0。则用户4开始为0,等待前面的结束。使用 sleep(rand()%5); 模拟线程执行时间,时间长度在0到4秒之间随机。

  • 初始化信号量 sem_WIN,初始值为3,表示同时最多允许3个线程执行。
  • 创建10个线程,每个线程都执行 th 函数。
  • 使用 pthread_join 等待所有线程完成。这确保了主线程在所有线程完成之前不会退出。
  • 销毁信号量 sem_WIN

区别:

信号量有顺序

互斥锁是自己上锁自己解锁,

*产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

产生死锁的原因主要是

(1) 因为系统资源不足。

(2) 进程运行推进的顺序不合适。

(3) 资源分配不当等。

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则

就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

相关推荐
武超杰4 小时前
Spring MVC进阶与SSM整合实战
java·spring·mvc
北岛寒沫4 小时前
北京大学国家发展研究院 中国经济专题 课程笔记(第三课 人口与劳动力)
经验分享·笔记·学习
dovens4 小时前
redis的下载和安装详解
数据库·redis·缓存
observe1014 小时前
arm汇编语言学习
学习
不吃香菜学java4 小时前
苍穹外卖-删除菜品
java·spring boot·spring·tomcat·log4j·maven
编程之升级打怪4 小时前
Python语言操作redis缓冲库的案例
数据库·redis·缓存
jiayong234 小时前
0基础学习VUE3 第 1 课:项目启动流程
前端·vue.js·学习
woniu_maggie4 小时前
SAP消息号修改处理与应用
后端·学习
今天又在摸鱼4 小时前
学习vue前必要的js语法
前端·vue.js·学习
程序员夏末4 小时前
【MySQL | 第一篇】 深入理解三大日志(undo Redo Bin)
数据库·mysql