IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux下的线程了解 -> 正文阅读

[系统运维]linux下的线程了解

线程的使用

线程概念

在这里插入图片描述

所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。我们可以通过多线程编程,使一个进程可以去执行多个不同的任务。相比多进程编程而言,线程享有共享资源,即在进程中出现的全局变量,每个线程都可以去访问它,与进程共享内存空间,使得系统资源消耗减少。

线程号

pthread_t
对于进程而言,每一个进程都有一个唯一对应的PID号来表示该进程,而对于线程而言,也有一个“类似于进程的PID号”,名为tid,其本质是一个pthread_t类型的变量。线程号与进程号是表示线程和进程的唯一标识,但是对于线程号而言,其仅仅在其所属的进程上下文中才有意义。
获取线程号

#include <pthread.h>
pthread_t pthread_self(void);

成功:返回线程号

创建线程

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
  • pthread_t *thread 参数为pthread_t指针,用来保存新建线程的线程号;
  • const pthread_attr_t *attr 参数表示了线程的属性,一般传入NULL表示默认属性;
  • void *(*start_routine) (void *) 参数是一个函数指针,就是线程执行的函数。这个函数返回值为void*,形参为void*。
  • void *arg 参数则表示为向线程处理函数传入的参数,若不传入,可用NULL填充。

实例

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

void *fun1(void *arg)
{
	while(1){
		printf("fun1:arg = %d Addr = %p\n",*(int *)arg,arg);
		sleep(1);
	}
}

void *fun2(void *arg)
{
	while(1){
		printf("fun2:arg = %d Addr = %p\n",(int)(long)arg,arg);
		sleep(1);
	}
}

int main()
{

	pthread_t tid1,tid2;
	int a = 50;
	int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	//sleep(1);
	ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	while(1){
		a++;
		sleep(1);
		printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);
	}
	return 0;
}

在这里插入图片描述
对于第四个参数向线程传入数据,在线程处理回调函数中,直接将void*数据转化为int类型即可,本质上是在传递变量a的值。
上述两种方法均可得到所要的值,但是要注意其本质,一个为地址传递,一个为值的传递。当变量发生改变时候,传递地址后,该地址所对应的变量也会发生改变,但传入变量值的时候,即使地址指针所指的变量发生变化,但传入的为变量值,不会受到指针的指向的影响。

结束与回收

线程退出

主动退出

pthread_exit

#include <pthread.h>
void pthread_exit(void *retval);

pthread_exit函数为线程退出函数,在退出时候可以传递一个 void* 类型的数据带给主线程,若选择不传出数据,可将参数填充为NULL。

被动退出

pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);

该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0。

线程回收

阻塞

pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

该函数为线程回收函数,默认状态为阻塞状态,直到成功回收线程后才返回。第一个参数为要回收线程的tid号,第二个参数为线程回收后接受线程传出的数据,可以指定NULL。

非阻塞

pthread_tryjoin_np

#define _GNU_SOURCE            
#include <pthread.h>
int pthread_tryjoin_np(pthread_t thread, void **retval);

该函数为非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回0,其余参数与pthread_join一致。

线程控制

当多个线程在运行过程中,去操作公共资源,如全局变量的时候,可能会发生彼此“矛盾”现象。
例如线程1企图想让变量自增,而线程2企图想要变量自减,两个线程存在互相竞争的关系导致变量难以达到期望值。
为了解决上述对临界资源的竞争问题,pthread线程引出了互斥锁来解决临界资源访问。通过对临界资源加锁来保护资源只被单个线程操作,待操作结束后解锁,其余线程才可获得操作权。

互斥量

当多个线程出现后,会遇到同时操作临界公共资源的问题,当线程操作公共资源时需要对线程进行保护加锁,防止其与线程在此线程更改变量时同时更改变量,待逻辑执行完毕后再次解锁,使其余线程再度开始竞争。
在这里插入图片描述

初始化互斥量

#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);

该函数初始化一个互斥量,第一个参数是改互斥量指针,第二个参数为控制互斥量的属性,一般为NULL。当函数成功后会返回0,代表初始化互斥量成功。

宏模块初始化互斥量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER; 

互斥量加锁/解锁

阻塞

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

lock函数与unlock函数分别为加锁解锁函数,只需要传入已经初始化好的pthread_mutex_t互斥量指针。成功后会返回0。
当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
特别注意的是,当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。
在使用互斥量的时候,尤其要注意使用pthread_cancel函数,防止发生死锁现象!

非阻塞

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);

该函数同样也是一个线程加锁函数,但该函数是非阻塞模式通过返回值来判断是否加锁成功,用法与上述阻塞加锁函数一致。

互斥量销毁

#include <pthread.h>
 int pthread_mutex_destory(pthread_mutex_t *mutex);

该函数是用于销毁互斥量的,传入互斥量的指针,就可以完成互斥量的销毁,成功返回0。

示例

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

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *fun1(void *arg);
void *fun2(void *arg);

int num = 0;

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;

    if(pthread_create(&tid1 ,NULL ,fun1 ,NULL) != 0){
        perror("func1 create: ");
        exit(-1);
    }
    if(pthread_create(&tid2, NULL, fun2, NULL) != 0){
        perror("func2 create: ");
        exit(-1);
    }

    pthread_join(tid1 ,NULL);
    pthread_join(tid2 ,NULL);
    pthread_mutex_destroy(&lock);

    return 0;
}

void *fun1(void *arg)
{
    int t = 5;
    pthread_mutex_lock(&lock);
    while(t--){
        printf("%s : num = %d\n",__FUNCTION__,num++);
    }
    pthread_mutex_unlock(&lock);
    pthread_exit(0);
}
void *fun2(void *arg)
{
    int t = 4;
    pthread_mutex_lock(&lock);
    while(t--){
        printf("%s : num = %d\n",__FUNCTION__,num--);
    }
    pthread_mutex_unlock(&lock);
    pthread_exit(0);
}

在这里插入图片描述

信号量

当多个线程出现后,同时会遇到无序执行的问题。有时候需要对线程的执行顺序做出限定,变引入了信号量,通过PV操作来控制线程的执行顺序。
信号量起通知作用,线程A在等待某件事,线程B完成了这件事后就可以给线程A发信号。
在这里插入图片描述

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);

sem_t *sem传入sem_t类型指针;
int pshared传入0代表线程控制,否则为进程控制;
unsigned int value表示信号量的初始值,0代表阻塞,1代表运行。
待初始化结束信号量后,若执行成功会返回0。

信号量P/V操作

阻塞

#include <pthread.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);

sem_wait函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行“sem-1”的操作。所谓的“sem-1”是与上述初始化函数中第三个参数值一致,成功执行会返回0。
sem_post函数会释放指定信号量的资源,执行“sem+1”操作。
通过以上2个函数可以完成所谓的PV操作,即信号量的申请与释放,完成对线程执行顺序的控制。

非阻塞

#include <pthread.h>
int sem_trywait(sem_t *sem);

此函数是信号量申请资源的非阻塞函数,功能与sem_wait一致,唯一区别在于此函数为非阻塞。

信号量销毁

#include <pthread.h>
int sem_destory(sem_t *sem);

该函数为信号量销毁函数,执行过后可将信号量进行销毁。

示例

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

sem_t sem1,sem2,sem3;

void *func1(void *arg);
void *func2(void *arg);
void *func3(void *arg);

int main()
{
    pthread_t tid1,tid2,tid3;

    if(sem_init(&sem1 , 0 , 1) != 0){
        //初始化时,直接给sem1 信号资源
        perror("fail sem1 init :");
        return -1;
    }
    if(sem_init(&sem2 , 0 , 0) != 0){
        perror("fail sem1 init :");
        return -1;
    }
    if(sem_init(&sem3 , 0 , 0) != 0){
        perror("fail sem1 init :");
        return -1;
    }

    if(pthread_create(&tid1 , NULL , func1 , NULL) != 0){
        perror("fail func1 create :");
        return -1;
    }
    if(pthread_create(&tid2 , NULL , func2 , NULL) != 0){
        perror("fail func2 create :");
        return -1;
    }
    if(pthread_create(&tid3 , NULL , func3 , NULL) != 0){
        perror("fail func2 create :");
        return -1;
    }

    pthread_join(tid1 , NULL);
    pthread_join(tid2 , NULL);
    pthread_join(tid3 , NULL);
    
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    sem_destroy(&sem3);

    return 0;
}

void *func1(void *arg)
{
    sem_wait(&sem1);
    printf("%s : Hello Come!\n",__FUNCTION__);
    sem_post(&sem2);
    pthread_exit(NULL);
}
void *func2(void *arg)
{
    sem_wait(&sem2);
    printf("%s : Hello Come!\n",__FUNCTION__);
    sem_post(&sem3);
    pthread_exit(NULL);
}
void *func3(void *arg)
{
    sem_wait(&sem3);
    printf("%s : Hello Come!\n",__FUNCTION__);
    pthread_exit(NULL);
}

在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 23:49:45  更:2022-04-01 23:52:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 0:04:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码