首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【linux学习指南】线程同步与互斥

【linux学习指南】线程同步与互斥

作者头像
学习起来吧
发布2025-02-14 08:35:59
发布2025-02-14 08:35:59
14300
代码可运行
举报
文章被收录于专栏:学习C/++学习C/++
运行总次数:0
代码可运行

📝线程互斥

🌠 库函数strncpy

🌉进程线程间的互斥相关背景概念

  • 临界资源:多线程执⾏流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起保护作⽤
  • 原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

🌉互斥量mutex

  • ⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程⽆法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来⼀些问题。

Makefile文件

代码语言:javascript
代码运行次数:0
运行
复制
bin=ticket
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)

$(bin):$(obj)
	$(cc) -o $@ $^ -lpthread
%.o:%.cc
	@echo "Comiling $< to $@"
	$(cc) -c $< -std=c++17

.PHONY:clean
clean:
	rm -f $(bin) $(obj)

.PHONY:test
test:
	echo $(src)
	echo $(obj)

代码:

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

int ticket = 100;

void* routine(void* args)
{
    char *id = (char*)args;
    // std::string id = static_cast<const char*>(args);
    while(1)
    {
        if(ticket > 0)
        {
            usleep(10000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }

    return nullptr;
}


int main(void)
{
    pthread_t t1, t2 , t3, t4;

    pthread_create(&t1, nullptr, routine, (void*)"thread 1");
    pthread_create(&t2, nullptr, routine, (void*)"thread 2");
    pthread_create(&t3, nullptr, routine, (void*)"thread 3");
    pthread_create(&t4, nullptr, routine, (void*)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    return 0;
}
  • if语句判断条件为真以后,代码可以并发的切换到其他进程
  • usleep这个模拟夜漫长业务的过程这个漫长的业务过程中,可能有多个线程会进入该代码段
  • --ticket操作本身就不是一个原子操作

取出ticket–部分的汇编代码

代码语言:javascript
代码运行次数:0
运行
复制
objdump -d a.out > test.objdump
 152   40064b:   8b 05 e3 04 20 00     mov       0x2004e3(%rip),%eax 
600b34 <ticket>
 153   400651:   83 e8 01                	 sub        $0x1,%eax
154   400654:   89 05 da 04 20 00       mov		%eax,0x2004da(%rip)  
600b34 <ticket>

--操作并不是原子操作而是对应三条汇编指令:

  • load将共享变量体的从内存加载到寄存器
  • update更新寄存器里面的只执行复议操作
  • store:将新值从寄存器写回共享变量ticket的内存地址

要解决以上问题需要做到三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他进程进入该临界区
  • 如果多个线程同时要求进入临界区的代码,并且临界区没有线程在执行,那么只能一个线程进入该临界区
  • 如果现场不在临界区中执行,那么该现场就不能阻止其他进程进入临界区

要做到这三点,本身是上就是需要一把锁,linux上提供这把锁叫互斥量

互斥量的接口 初始化互斥量的两种方法

  • 方法1:静态分配
代码语言:javascript
代码运行次数:0
运行
复制
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:这是一个指向 pthread_mutexattr_t 类型对象的指针,该类型用于定义互斥锁的属性。如果将其设置为 NULL

销毁互斥量 销毁互斥量需要注意:

  • 使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁⼀个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁
代码语言:javascript
代码运行次数:0
运行
复制
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

代码语言:javascript
代码运行次数:0
运行
复制
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回o,失败返回错误号

用pthread_ lock时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

改进上面的售票系统:

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

int ticket = 100;
pthread_mutex_t mutex;


void* routine(void* args)
{
    char *id = (char*)args;
    // std::string id = static_cast<const char*>(args);
    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;
        }
    }

    return nullptr;
}


int main(void)
{
    pthread_t t1, t2 , t3, t4;

    pthread_create(&t1, nullptr, routine, (void*)"thread 1");
    pthread_create(&t2, nullptr, routine, (void*)"thread 2");
    pthread_create(&t3, nullptr, routine, (void*)"thread 3");
    pthread_create(&t4, nullptr, routine, (void*)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    pthread_mutex_destroy(&mutex);
    return 0;
}

RAII风格的互斥锁,C++11也有,比如: std : : mutex mtx; std : : lock_guard guard ( mtx ) ;

🌠线程同步

🌉条件变量

  • 当⼀个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列中。这种情况就需要⽤到条件变量。

🌉同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题,叫做同步
  • 竞态条件:因为时序问题,⽽导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也 不难理解

🌉 条件变量函数

初始化

代码语言:javascript
代码运行次数:0
运行
复制
int pthread_cond_init(pthread_cond_t *restrict cond,   const pthread_condattr_t*restrict attr);

参数: cond:要初始化的条件变量 attr: NULL

销毁:

代码语言:javascript
代码运行次数:0
运行
复制
int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满⾜

代码语言:javascript
代码运行次数:0
运行
复制
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrictmutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待

代码语言:javascript
代码运行次数:0
运行
复制
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

简单案例:

  • 我们先使用PTHREAD_COND/MUTEX_INITIALIZER进行测试,对其他细节暂不追究
  • 然后将接口更改成为使用pthread_cond_init/pthread_cond_destroy的方式,方便后续进行封装
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* active(void* args)
{
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        std::cout<< name << "活动..." << std::endl;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t t1, t2;

    pthread_create(&t1, nullptr, active, (void*)"thread -1");
    pthread_create(&t2, nullptr, active, (void*)"thread -2");

    sleep(3);
    while(true)
    {
        //对比测试
        pthread_cond_signal(&cond);//唤醒一个线程
        // pthread_cond_broadcast(&cond);//唤醒所有线程
        sleep(1);

    }

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 📝线程互斥
  • 🌠 库函数strncpy
    • 🌉进程线程间的互斥相关背景概念
    • 🌉互斥量mutex
  • 🌠线程同步
    • 🌉条件变量
    • 🌉同步概念与竞态条件
    • 🌉 条件变量函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档