linux:线程id及线程互斥

线程的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() //这样就不会有负数啦
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值