linux:线程id及线程互斥

news2024/10/25 19:43:12

线程的tid不像进程,那不是他真正的id,也不是内核的lwp,而是由pthread库维护的一个唯一值

给用户提供的线程ID,不是内核中的lwp,而是pthread库维护的一个唯一值

库内部也要承担对线程的管理

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void ToHex(pthread_t tid,char*buffer){
    snprintf(buffer,128,"0x%lx",(unsigned long)tid);
}


void* gothread(void* arg){
    while(1){
        char buffer[128];
        ToHex(pthread_self,buffer);
        printf("arg %s is running\n",buffer);
        sleep(1);
    }
}


int main(){
    pthread_t tid;
    pthread_create(&tid,NULL,gothread,(void*)"thread-1");
    char buffer[128];
    ToHex(tid,buffer);
    printf("new thread tid:%s\n",buffer);
    pthread_join(tid,NULL);
    return 0;
    }

哦,这里报的错是说我的pthread_t的类型可能不匹配,我传回的是pthread_t类型,但是在打印的时候不兼容,可能会被识别为指针对象

当然,也能正常运行

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void ToHex(unsigned long tid,char*buffer){
    snprintf(buffer,128,"0x%lx",( unsigned long)tid);
}


void* gothread(void* arg){
    while(1){
        char buffer[128];
        ToHex((unsigned long)pthread_self,buffer);
        printf("arg %s is running\n",buffer);
        sleep(1);
    }
}


int main(){
    pthread_t tid;
    pthread_create(&tid,NULL,gothread,(void*)"thread-1");
    char buffer[128];
    ToHex((unsigned long)tid,buffer);
    printf("new thread tid:%s\n",buffer);
    pthread_join(tid,NULL);
    return 0;
}

tid在这里就是一个地址

ls /lib/x86_64-linux-gnu/libpthread.so.0 -l

这是Linux系统下的一个线程库

pthread库的本质是一个文件,我们创建进程的时候,本质上是把线程库加载到内存,映射到进程(也就是真实的地址空间)

那么库是如何对线程进行管理的?

就像操作系统对进程的管理一样,struct pthread里存储的是线程在用户级的最基本的属性,线程栈是用户级别的独立栈结构

库对线程进行先描述再组织

在库中创建描述线程的相关结构体字段属性,管理的时候只需要找到对应的线程控制块的地址就可以了

所以Linux下的线程=pthread库中的属性集+LWP

操作系统没有线程,那它势必就要为我们提供LWP的系统调用

大概就像这样:

#define _GNU_SOURCE
#include <sched.h>
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
/* pid_t *parent_tid, void *tls, pid_t *child_tid */ );

clone来创建线程(也能创建进程)

封装一个自己的线程库

满屏警告呃呃

把比较严重的处理了一下

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
//创建线程属性的结构体
typedef struct{
    pthread_t tid;
    char* name[128];
    int running;
}Thread;
//线程执行的函数
void gopthread(void* args){
    Thread* thread=(Thread*)args;
    while(thread->running){
        printf("%s is running\n", thread->name);
        sleep(1);
    }
    return NULL;
}
//创建
void mypthread_Create(Thread *threads){
    threads->running=1;
    pthread_create(&threads->tid,NULL,gopthread,(void*)threads);//把参数作为结构体传入
}

//停止
void mypthread_Stop(Thread *threads){
    threads->running=0;
}

//等待
void mypthread_Join(Thread *threads){
    pthread_join(threads->tid,NULL);
}

int main(){
    Thread threads[SIZE];
    //创建线程
    for(int i=0;i<SIZE;i++){
        snprintf(threads[i].name,sizeof(threads[i].name),"thread-%d",i+1);
        mypthread_Create(&threads[i]);
    }
    sleep(5);//留给线程运行
    //停止线程
    for(int i=0;i<SIZE;i++){
        mypthread_Stop(&threads[i]);
    }
    //等待
     for(int i=0;i<SIZE;i++){
        mypthread_Join(&threads[i]);
    }
    return 0;
}

线程互斥

我们用线程来模拟一个抢票的过程,在票只有十张的时候,创建三个线程同时抢票

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int tickets=10;

void*  gopthread(void* args){
    char* name=(char*)args;
    while(tickets>0){
        tickets--;
        printf("%s pthread pay a ticket,remain %d\n",name,tickets);
        sleep(1);
        
    }
    pthread_exit(args);
    return args;
}


int main(){
    char names[10][128];
    pthread_t tids[10];
    for(int i=0;i<3;i++){
        snprintf(names[i],sizeof(names[i]),"thread-%d",i);
        pthread_create(&tids[i], NULL,gopthread,names[i]);
    }
    
     for(int i=0;i<3;i++){
        void* name=NULL;
        pthread_join(tids[i],&name);
        printf("%s quit...\n",(char*)name);
    }
    
    return 0;
}

我模拟了很多次,也没有出现线程互斥的现象哈。。但是这是概率事件,我没执行出来

可能会出现进入线程执行的函数内的时候,票有余量;但是其他线程在此时突然进行了票的--,就会出现互斥现象

我们之前提到过信号量是原子的,而票的--并不是原子性的,本质上其实并不是原子性的;转成汇编后执行重读数据--->数据--->写回数据

load :将共享变量ticket从内存加载到寄存器中

update : 更新寄存器里面的值,执行-1操作

store :将新值,从寄存器写回共享变量ticket的内存地址

所以我们需要进行互斥行为,来防止数据竞争

并且在高并发的时候执行临界区代码,而临界区没有线程在执行,那么只能有一个线程进入临界区

继续偷励志轩的图

补充一下不全的man:

sudo apt-get install glibc-doc
#include <pthread.h>
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;   
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
 
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

锁包含互斥量,锁包含很多种:

  • 互斥锁(Mutex):确保同一时刻只有一个线程访问共享资源。

  • 读写锁(Read-Write Lock):允许多个线程同时读,但写操作互斥。

  • 自旋锁(Spin Lock):线程在等待锁时不断循环检查,而不进入休眠。

  • 递归锁(Recursive Lock):允许同一个线程多次获取锁而不死锁。

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁

不要销毁一个已经加锁的互斥量

已经销毁的互斥量,要确保后面不会有线程再尝试加锁

也就是对临界资源、临界区的代码的保护

进入临界区之前要加锁,出了临界区要解锁;进锁的时候要并行改串行

并行:多个线程独立的执行

串行:多个线程对同一片临界资源操作,需要像卖票一样排队

先写一下吧:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int tickets=10;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;

void*  gopthread(void* args){
    char* name=(char*)args;
    while(1){
        if(tickets>0){    pthread_mutex_lock(&gmutex);
            tickets--;
            printf("%s pthread pay a ticket,remain %d\n",name,tickets);
            pthread_mutex_unlock(&gmutex);
        }
        else{
            pthread_mutex_lock(&gmutex);//没票了,锁上
            //pthread_exit(args);没必要,线程会自己退出
            break;
        }
        sleep(1);
    }
    return args;
}


int main(){
    char names[10][128];
    pthread_t tids[10];
    for(int i=0;i<10;i++){
        snprintf(names[i],sizeof(names[i]),"thread-%d",i);
        pthread_create(&tids[i], NULL,gopthread,names[i]);
    }
    
     for(int i=0;i<10;i++){
        void* name=NULL;
        pthread_join(tids[i],&name);
        printf("%s quit...\n",(char*)name);
    }
    
    return 0;
}

此处为什么没有线程退出?

因为我的检查票数是否>0的语句放在了锁外,这意味着同时肯有很多线程访问我的票数是不是>0,如果A线程和B线程同时都发现票数==1,那么他们都进入减票数的if语句中,而A的tickets--之后,B处在--的语句里,但是没有票可--了,就会像我一样阻塞,也退不出去,这是线程竞态

而且应该在确定票数>0为假时候及时解锁,避免只锁不解锁的死锁状态

应该这样:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int tickets=1000;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;

void*  gopthread(void* args){
    char* name=(char*)args;
    while(1){
        pthread_mutex_lock(&gmutex);//检查票数和买票的线程应该在同一个锁内
        if(tickets>0){   
            tickets--;
            printf("%s pthread pay a ticket,remain %d\n",name,tickets);
            pthread_mutex_unlock(&gmutex);
        }
        else{
            pthread_mutex_unlock(&gmutex);//没票了,锁上
            
            break;
        }
        sleep(1);
    }
    return args;
}


int main(){
    char names[10][128];
    pthread_t tids[10];
    for(int i=0;i<10;i++){
        snprintf(names[i],sizeof(names[i]),"thread-%d",i);
        pthread_create(&tids[i], NULL,gopthread,names[i]);
    }
    
     for(int i=0;i<10;i++){
        void* name=NULL;
        pthread_join(tids[i],&name);
        printf("%s quit...\n",(char*)name);
    }
    
    return 0;
}

这样就正常了

所以锁的位置很重要,锁是保护临界资源的,线程阻塞的前提是看见锁

锁本身也是临界资源(好熟悉的话,你说对不对信号量?)

所以锁像信号量一样,本身是原子性的;区别是信号量本身可以让多个线程来访问临界资源,而锁就是为了锁上后只让一个线程访问

申请锁成功的线程即使被调度走了,只要锁没开,其他线程就不可以执行临界区的代码

所以访问临界区对其他线程是原子的

这个流程在第一个有锁的线程在还完了锁之后不能立马申请(要二次申请必须排队,其他线程也必须排队),也就是说在保证临界资源安全的情况下让访问顺序合理公平

这就是:线程同步!

可以是严格的顺序性,也可以是宏观上具有相对的顺序性

原理角度理解锁

如何理解申请锁成功就允许进入临界区?申请锁失败就不允许进入临界区

允许进入临界区就是申请锁成功,pthread_mutex_lock()函数会返回

不允许进入临界区就是申请锁失败,pthread_mutex_lock()函数不返回,线程就阻塞了(阻塞之后在pthread_mutex_lock内部被重新唤醒,重新申请锁)

一个CPU只有一套寄存器,被所有的线程共享,但是寄存器内部的数据是执行流(线程、进程这类)的数据属于执行流私有的数据

CPU在执行代码时,对应的载体是进程或线程,数据到了内存中就是共享的

把数据从内存移动到CPU寄存器中本质是把数据从共享变成线程私有

放一放我学协程时候拿go写的模拟抢票:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

var ticktes = 10
var mutex sync.Mutex  //定义一个互斥锁的对象
var wg sync.WaitGroup //同步等待组对象

func main() {
	wg.Add(4)
	fmt.Println("陶吉吉演唱会,开始!")
	go TicktesSaler("1号")
	go TicktesSaler("2号")
	go TicktesSaler("3号")
	go TicktesSaler("4号")
	wg.Wait()
	//time.Sleep(1 * time.Second)
	fmt.Println("All Done")
}
func TicktesSaler(name string) {
	rand.Seed(time.Now().UnixNano())
	defer wg.Done()
	for {
		mutex.Lock() //从这行开始上锁,只有一个goroutine可以执行
		if ticktes > 0 {
			time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
			ticktes--
			fmt.Printf("%sTicktesSold卖出,余量:%d\n", name, ticktes) //票数出现负数是因为1号判定完票数>0后进入if分支先睡觉
			//睡觉的时候被别的goroutine抢占了资源,等睡醒的时候票已经为0了,但是因为已经进入>0的分支,所以只能继续执行,变成负数

		} else {
			mutex.Unlock() //解锁,输出售罄需要解锁
			fmt.Printf("%s做不了自己了\n", name)

			break
		}
		mutex.Unlock() //这样就不会有负数啦
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2223504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024青龙面板京东教程

一、接上文《2024最新青龙面板安装教程》 腾讯云轻量服务器2核2G4M&#xff0c;只要79一年&#xff0c;可续费一次。 购买地址&#xff1a;https://curl.qcloud.com/LpLkvjq1 二、拉库 拉库前请打开青龙面板-配置文件 第18行 GithubProxyUrl“” 双引号中的内容清空 复制以…

代理 IP 对于鸿蒙开发者的意义与帮助

华为推出的鸿蒙操作系统以其独特的分布式架构和强大的性能&#xff0c;吸引了众多开发者的目光。而在鸿蒙开发的过程中&#xff0c;代理 IP 技术也发挥着重要的作用&#xff0c;为开发者带来了诸多意义与帮助。 一、提供更广泛的测试环境 对于鸿蒙开发者来说&#xff0c;确保应…

高效数据集成:聚水潭采购入库单与金蝶云星空

聚水潭采购入库单与金蝶云星空的高效数据集成案例分享 在企业日常运营中&#xff0c;采购入库单的数据处理和管理是至关重要的一环。为了实现聚水潭采购入库单到金蝶云星空的无缝对接&#xff0c;我们采用了轻易云数据集成平台&#xff0c;成功配置并运行了“聚水潭采购入库单…

钉钉录播抓取视频

爬取钉钉视频 免责声明 此脚本仅供学习参考&#xff0c;切勿违法使用下载他人资源进行售卖&#xff0c;本人不但任何责任! 仓库地址: GItee 源码仓库 执行顺序 poxyM3u8开启代理getM3u8url用于获取m3u8文件userAgent随机请求头downVideo|downVideoThreadTqdm单线程下载和…

荣耀MagicOS 9.0发布会及开发者大会丨一图读懂应用服务及商业合作分论坛

更多优质流量变现服务&#xff0c;可点击荣耀广告变现服务查看&#xff1b; 荣耀远航计划——应用市场【耀闪行动】全新上线&#xff0c;更多激励及资源扶持可点击荣耀应用市场耀闪行动查看。

Zookeeper实战 集群环境部署

1、概述 今天我们来学习一下Zookeeper集群相关的内容&#xff0c;本文主要的内容有集群环境的搭建&#xff0c;集群常见的问题和对应的解决方案。 2、集群环境搭建 2.1、准备工作 首先我们准备好安装包&#xff0c;创建好集群部署的路径。将解压后的安装文件复制三分。这里…

水轮发电机油压自动化控制系统解决方案介绍

在现代水电工程中&#xff0c;水轮机组油压自动化控制系统&#xff0c;不仅直接关系到水轮发电机组的安全稳定运行&#xff0c;还影响着整个水电站的生产效率和经济效益。 一、系统概述 国科JSF油压自动控制系统&#xff0c;适用于水轮发电机组调速器油压及主阀&#xff08;蝶…

【功能安全】 独立于环境的安全要素SEooC

目录 01 SEooC定义 02 SEooC开发步骤 03 SEooC开发示例 04 SEooC问答 01 SEooC定义 缩写: SEooC:Safety Element out of Context独立于环境的安全要素 SEooC出处:GB/T34590.10—2022,第9章节 SEooC与相关项什么关系? SEooC可以是系统、系统组合、子系统、软件组件、…

【Unity】游戏UI中添加粒子特效导致穿层问题的解决

这里介绍一下简易的ui系统中&#xff0c;添加粒子特效导致的穿层问题 首先是在ui界面中添加粒子特效预制体&#xff0c;这个时候&#xff0c;控制这个粒子显示层级的有两个方面 上图中&#xff0c;如果你的Sorting Layer ID的值&#xff08;Layer排序&#xff09;是大于当前C…

安康旅游指南:基于SpringBoot的网站开发实践

第一章 绪论 1.1 研究现状 时代的发展&#xff0c;我们迎来了数字化信息时代&#xff0c;它正在渐渐的改变着人们的工作、学习以及娱乐方式。计算机网络&#xff0c;Internet扮演着越来越重要的角色&#xff0c;人们已经离不开网络了&#xff0c;大量的图片、文字、视频冲击着我…

可视化大屏的C位放啥(04):园区鸟瞰图,方寸之间显示海量数据

在可视化大屏的 C 位放置园区鸟瞰图&#xff0c;可谓独具匠心。 鸟瞰图以宏观视角呈现整个园区风貌&#xff0c;让人一眼便对园区布局有清晰认知。同时&#xff0c;通过巧妙的设计&#xff0c;可在这方寸之间展示海量数据。 如园区内各企业的生产数据、能耗情况、人员流动等信…

【学习笔记】强化学习

李宏毅深度强化学习 笔记 课程主页&#xff1a;NTU-MLDS18 视频&#xff1a;youtube B站 参考资料&#xff1a; 作业代码参考 纯numpy实现非Deep的RL算法 OpenAI tutorial 文章目录 李宏毅深度强化学习 笔记1. Introduction2. Policy Gradient2.1 Origin Policy Gradient2.2…

Matlab 车牌识别技术

1.1设计内容及要求&#xff1a; 课题研究的主要内容是对数码相机拍摄的车牌&#xff0c;进行基于数字图像处理技术的车牌定位技术和车牌字符分割技术的研究与开发&#xff0c;涉及到图像预处理、车牌定位、倾斜校正、字符分割等方面的知识,总流程图如图1-1所示。 图1-1系统总…

【问题解决】Flink在linux上运行成功但是无法访问webUI界面

一&#xff0c;问题 在搭建Flink的时候&#xff0c;已经在linux服务器上运行了./start-cluster.sh&#xff0c; 而且日志显示已经成功了。 服务器上也没有开启防火墙 正常来说应该能通过ip:8081来访问(8081是Flink WebUI的默认端口)&#xff0c;但是访问的时候&#xff0c;显示…

Redis未授权访问及配合SSRF总结

Redis是一个开源的内存数据库&#xff0c;它用于存储数据&#xff0c;并提供高性能、可扩展性和丰富的数据结构支持。 Redis复现文章较全 Redisssrf漏洞利用探测内网 RedisInsight/RedisDesktopManager可视化连接工具 漏洞原理 &#xff08;1&#xff09;redis绑定在 0.0.…

C++类与对象四

C类与对象&#xff08;四&#xff09; 上期我们介绍了构造函数和析构函数&#xff0c;这期我们来介绍拷贝函数和运算符重载 拷贝函数 在现实生活中&#xff0c;可能存在另一个你。 那在C中&#xff0c;我们是否能创建一个与已知对象一样的新对象呢&#xff1f; 拷贝构造函数…

六.python面向对象

学过C或者Java的同学一定了解过面向对象的相关内容&#xff0c;编程语言一般分为两种设计方式&#xff1a;面向对象、面向过程&#xff0c;早期的编程语言多是面向过程的&#xff0c;由多个过程组合在一起&#xff0c;而Python在设计的时候就是一种面向对象的语言&#xff0c;因…

[学习笔记]线段树(全)

线段树是一种可以处理区间问题的优秀数据结构. 线段树是一颗二叉树, 其中的每一个节点都代表了某个区间的信息. 普通线段树 这里默认您已经会了以下操作: 建树(以单点修改的形式)单点修改/查询区间查询 如果不会的话请见OI Wiki 着重讲解区间修改中 tag 的用法 对于区间修…

InternVL-1.1: Enhance Chinese and OCR Capabilities

Blog:https://internvl.github.io/blog/2024-01-24-InternVL-1.1/ 指南:https://internvl.readthedocs.io/en/latest/internvl1.1/introduction.html InternVL-Chat-V1-1 结构类似于 LLaVA,包括一个 ViT、一个 MLP 投影器和一个 LLM。如上图所示,我们通过一个简单的 MLP …

ubuntu服务器离线安装pytorch(cpu版本)

一、查看服务器是否有nvidia显卡&#xff08;无输出则没有nvidia显卡&#xff0c;则不需要安装nvidia驱动、cuda、cudnn&#xff09; lspci | grep -i nvidia 二、本地下载对应版本的torch&#xff08;对应python版本和linux系统&#xff09; 注意&#xff1a;cpu版本&#…