首页 > 其他分享 >初学线程

初学线程

时间:2024-09-30 19:50:46浏览次数:10  
标签:int cond 互斥 初学 mutex pthread 线程

线程概念

线程是进程中的⼀个执行单元,负责当前进程中程序的执行,⼀个进程中至少有⼀个线程
⼀个进程中是可以有多个线程
多个线程共享同一个进程的资源,每个线程参与操作系统的统一调度

进程相当于是由进程资源+主线程+子线程,组合而来

程序由进程进行执行,进程由线程执行

线程与进程区别

内存空间
          一个进程中多个线程共享同一个内存空间
          多个进程拥有独立的内存空间
进程/线程间通讯
        线程间通讯方式简单
        进程间通讯方式复杂

并发操作,线程比进程更节约资源

联系紧密的任务在并发时优先选择多线程,如果任务之间比较独立,在并发时建议选择多进程

 线程资源分配

共享进程的资源

        同一块地址空间

        文件描述符表

        每种信号的处理方式(如:SIG_DFL,SIG_IGN或者自定义的信号优先级)

        当前工作目录

        用户id和组id

独立的资源:

        线程栈

        每个线程都有私有的上下文信息。

        线程ID

        寄存器的值

        errno变量

        信号屏蔽字以及调度优先级

线程相关指令

在 Linux 系统有很多命令可以查看进程,例如 pidstat 、top 、ps ,也可以查看一个进程下的线程

1.pidstat

选项
-t:显示指定进程所关联的线程
-p:指定进程pid

2.top 命令

top 命令查看某一个进程下的线程,需要用到 -H 选项在结合 -p 指定 pid

3.ps命令

ps 命令结合 -T 选项就可以查看某个进程下所有线程

线程的创建

创建线程调用 pthread_create 函数

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *
(*start_routine) (void *), void *arg);

thread:线程ID变量指针
 attr:线程属性,默认属性可设置为NULL
 start_routine:线程执行函数
 arg:线程执行函数的参数

一旦子线程创建成功,则会被独立调度执行,并且与其他线程并发执行

pthread_t thread_id_a,thread_id_b;
        int result = pthread_create(&thread_id_a,NULL,do_thread_funA,NULL);

注意:在编译时需要链接 -lpthread[Compile and link with -pthread]

线程的退出

线程退出使用 pthread_exit 函数

函数原型 void pthread_exit(void *retval);

retval:线程返回值,通过指针传递

1.当主线程调用pthread_exit函数时,进程不会结束,也不会导致其他子线程退出
2. 任何线程调用exit函数会让进程结束

线程的等待

主线程需要等待子线程退出,并释放子线程资源

线程等待调用 pthread_join函数,会阻塞调用线程

int pthread_join(pthread_t thread, void **retval);

thread:线程 ID
retval:获取线程退出值的指针

// 线程执行函数
void* do_thread_function(void* args)
{
        printf("do thread ....\n");
        pthread_exit(NULL);
}
int main()
{
        pthread_t thread_id;
        int result = pthread_create(&thread_id,NULL,do_thread_function,NULL);
        if(result!=0)
       {
                fprintf(stderr,"pthread error:%s\n",strerror(result));
                exit(EXIT_FAILURE);
       }
        printf("thread id is %ld\n",thread_id);
        pthread_join(thread_id,NULL);
        return 0;
}

线程分离

线程分为可结合的可分离的

可结合
        可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源
        (如栈)是不释放的。
        线程创建的默认状态为可结合的,可以由其他线程调用 pthread_join函数等待子线程退出并
        释放相关资源

可分离
不能被其他线程回收或者杀死的,该线程的资源在它终止时由系统来释放

 pthread_detach 函数

int pthread_detach(pthread_t thread);

设置在线程退出后,由操作系统自动释放该线程的资源

注意:线程分离函数不会阻塞线程的执行

 多个线程的创建

创建多个线程时,一般由主线程统一创建,并等待释放资源或者分离线程,不要递归创建
1. 多个线程如果任务相同,则可以使用同一个线程执行函数
2. 多个线程如果任务不同,则可以使用不同的线程执行函数

// 线程执行函数
void* do_thread_funA(void* args)
{
        printf("do thread A\n");
        pthread_exit(NULL);
}
void* do_thread_funB(void* args)
{
        printf("do thread b\n");
        pthread_exit(NULL);
}

线程间通信

为什么需要线程通信?
线程是操作系统调度的最小单元,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。
线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺

主线程向子线程传递

 还记得pthread_create函数么

可以传很多不止int,如果你想传多种数据,可以创建结构体,传指针就行了

子线程向主线程传递

在子线程将需要返回的值存储在 pthread_exit 函数中的 retval 参数中
在主线程中通过 pthread_join 函数的第2个参数 retval 得到返回, pthread_join 函数会将线程的
返回值(指针)保存到 retval 中

线程同步

线程同步:是指在互斥的基础上,通过其它机制实现访问者对 资源的有序访问。
条件变量:线程库提供的专门针对线程同步的机制
线程同步比较典型的应用场合就是 生产者与消费者

线程互斥锁

线程的主要优势在于能够通过全局变量来共享信息,不过这种便捷的共享是有代价的:
1.必须确保多个线程不会同时修改同一变量
2.某一线程不会读取正由其他线程修改的变量,实际就是 不能让两个线程同时对临界区进行访问
3.线程互斥锁则可以用于解决多线程资源竞争问题

 举个例子

int global = 0;
//线程执行函数
void* do_thread(void* argv)
{
        //循环对global进行加1操作
        int loops = *(int*)argv;
        for(int i=0;i<loops;i++)
       {
                int temp = global;
                temp++;
                global=temp;
       }
        pthread_exit(NULL);
}
int main(int argc,char* argv[])
{
        if(argc !=2)
       {
                fprintf(stderr,"arguments must be 2:< cmd > <count>\n");
                exit(EXIT_FAILURE);
       }
        // 获取循环次数
        int loopCount = atoi(argv[1]);
        // 循环的方式创建两个线程
        pthread_t tids[2]={0};
        int err;
        for(int i=0;i<2;i++)
       {
                err = pthread_create(tids+i,NULL,do_thread,&loopCount);
                if(err!=0)
               {
                        fprintf(stderr,"pthread_create 
failed:%s\n",strerror(err));
                        exit(EXIT_FAILURE);
               }
       }
        pthread_join(tids[0],NULL);
        pthread_join(tids[1],NULL);
        // 打印全局变量
        printf("global=%d\n",global);
        return 0;
}

这里需要将数字字符串转换为整数 :int atoi(const char *nptr);

当循环多次后

线程之间资源的竞争会导致其出错

这时就要用到互斥锁

1. 线程互斥锁工作机制
        当线程A获得锁,另外一个线程B在获得锁时则会阻塞,直到线程A释放锁,线程B才会获得锁。
2. 线程互斥锁工作原理
        本质上是一个pthread_mutex_t类型的变量,假设名为 v
        当 v = 1,则表示当前临界资源可以竞争访问 ,得到互斥锁的线程则可以访问,此时 v = 0
        当 v = 0,则表示临界资源正在被某个线程访问,其他线程则需要等待
3. 线程互斥锁的特点
        互斥锁是一个 pthread_mutex_t类型的变量,就代表一个互斥锁
        如果两个线程访问的是同一个 pthread_mutex_t 变量,那么它们访问了同一个互斥锁
        对应的变量定义在 pthreadtypes.h 头文件中,是一个共用体中包含一个结构体

互斥锁的初始化

线程互斥锁的初始化方式主要分为两种
1. 静态初始化
定义 pthread_mutex_t 类型的变量,然后对其初始化为 PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2. 动态初始化
动态初始化主要涉及两个函数 pthread_mutex_init 函数 与 pthread_mutex_destroy 函数

 pthread_mutex_init

函数原型

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

mutex:线程互斥锁对象指针

attr:线程互斥锁属性

pthread_mutex_destroy

函数原型 
int pthread_mutex_destroy(pthread_mutex_t *mutex);

mutex:线程互斥锁指针

互斥锁的操作

线程互斥锁的操作主要分为 获取锁(lock) 与 释放锁(unlock)

获取锁的函数:pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

mutex:线程互斥锁指针

释放锁的函数:pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

解除互斥锁锁定状态,解除后,所有线程可以重新竞争锁

mutex:线程互斥锁对象的指针

静态

// 互斥锁静态初始化
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
//线程执行函数
void* do_thread(void* argv)
{
        //循环对global进行加1操作
        int loops = *(int*)argv;
        for(int i=0;i<loops;i++)
       {
                // 获取锁,一旦获取成功,则获取临界资源的访问资格,否则会阻塞当前线程
                pthread_mutex_lock(&mtx);
                int temp = global;
                temp++;
                global=temp;
                // 释放锁
                pthread_mutex_unlock(&mtx);
       }
        pthread_exit(NULL);
}

动态:

// 互斥锁变量
pthread_mutex_t mtx;
int main(int argc,char* argv[])
{
        if(argc !=2)
       {
                fprintf(stderr,"arguments must be 2:< cmd > <count>\n");
                exit(EXIT_FAILURE);
       }
        pthread_mutex_init(&mtx,NULL);
        // 获取循环次数
        int loopCount = atoi(argv[1]);
        // 循环的方式创建两个线程
        pthread_t tids[2]={0};
        int err;
        for(int i=0;i<2;i++)
       {
                err = pthread_create(tids+i,NULL,do_thread,&loopCount);
                if(err!=0)
               {
                        fprintf(stderr,"pthread_create 
failed:%s\n",strerror(err));
                        exit(EXIT_FAILURE);
               }
       }
        pthread_join(tids[0],NULL);
        pthread_join(tids[1],NULL);
        pthread_mutex_destroy(&mtx);
        return 0;
}

生产者消费者模型

1. 在这个模型中,包括生产者线程与消费者线程。通过线程来模拟多个线程同步的过程
2. 在这个模型中,需要以下组件
仓库 : 用于存储产品,一般作为共享资源
生产者线程 : 用于生产产品
消费者线程 : 用于消费产品
3. 原理
当仓库没有产品时,则消费者线程需要等待,直到有产品时才能消费
当仓库已经装满产品时,则生产者线程需要等待,直到消费者线程消费产品之后

生产者与消费者模型同步

基于互斥锁实现生产者与消费者模型实现

// 静态互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int product_number=0;
void* thread_handle(void* arg)
{
        // 生产产品的数量
        int cnt = atoi((char*)arg);
        for(int i=1;i<=cnt;i++)
       {
                // 获取锁
                pthread_mutex_lock(&mutex);
                printf("thread[%ld] produces a product, the number of 
products is %d.\n",pthread_self(),++product_number);
                // 释放锁
                pthread_mutex_unlock(&mutex);
       }
        pthread_exit(NULL);
}
// command format[./a.out 1 2 3],1 2 3表示生产者线程生产的产品数量
int main(int argc,char* argv[])
{
        int total_of_production= 0;// 生产产品的数量
        int total_of_consumption=0;// 消费产品的数量
        if(argc < 2)
       {
                fprintf(stderr,"command argument: ./a.out <...product 
quantity>\n");
                exit(EXIT_FAILURE);
       }
        pthread_t tid;
        for(int i=1;i<argc;i++)
       {
                total_of_production += atoi(argv[i]);
                int err = pthread_create(&tid,NULL,thread_handle,
(void*)argv[i]);
                if(err > 0)
               {
                        fprintf(stderr,"pthread_create 
error:%s\b",strerror(err));
                        exit(EXIT_FAILURE);
               }
       }
        // 消费产品
        while(1)
       {
                pthread_mutex_lock(&mutex);
                while(product_number > 0)
               {
                        printf("consumption a product ,the number of products 
is %d.\n",--product_number);
                  total_of_consumption++;
                        sleep(1);
               }
                pthread_mutex_unlock(&mutex);
                if(total_of_production == total_of_consumption)
               {
                        break;
               }
       }
        return 0;
}

条件变量

上面示例代码的不足:
主线程(消费者线程)需要不断查询是否有产品可以消费,如果没有产品可以消费,也在运行程序,包括获
得互斥锁、判断条件、释放互斥锁,非常消耗 cpu 资源。
使用条件变量进行修改
条件变量 允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知

 

初始条件变量

条件变量的本质为 pthread_cond_t 类型的变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞在这个条件变量上的线程。

条件变量的初始化分为静态初始化与动态初始化(与互斥锁相似)
1. 静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2. 动态初始化
pthread_cond_init 函数

        int pthread_cond_init(pthread_cond_t *restrict cond,const 
        pthread_condattr_t *restrict attr);

        cond:条件变量指针
        attr:条件变量属性

pthread_cond_destroy函数

        int pthread_cond_destroy(pthread_cond_t *cond);

 

1:消费者线程判断消费条件是否满足(仓库是否有产品),如果有产品则可以消费,然后解锁
2 :当条件满足时(仓库产品数量为0),则调用 pthread_cond_wait 函数,这个函数具体做的事
情如下:
在线程睡眠之前,对互斥锁解锁
让线程进入到睡眠状态
等待条件变量收到信号时,该函数重新竞争锁,并获取锁
3 :重新判断条件是否满足,如果满足,则继续调用 pthread_cond_wait 函数
4 :唤醒后,从 pthread_cond_wait 返回,条件不满足,则正常消费产品

5 :释放锁,整个过程结束

实现这个模型还需要几个函数

pthread_cond_wait

        int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict 
        mutex);

        函数功能 
        阻塞线程,等待唤醒
        函数参数
        cond:条件变量指针
        mutex:相关联的互斥锁指针

1.条件变量需要与互斥锁结合使用,先获得锁才能进行条件变量的操作
2.调用函数后会释放锁,并阻塞线程
3.一旦线程唤醒,需要重新竞争锁,重新获得锁之后,pthread_cond_wait 函数返回

pthread_cond_signa

        int pthread_cond_signal(pthread_cond_t *cond);
        函数功能 
        唤醒所有阻塞在某个条件变量上的线程

pthread_cond_broadcast函数

        int pthread_cond_broadcast(pthread_cond_t *cond);
        函数功能 
        唤醒所有阻塞在某个条件变量上的线程

pthread_cond_signal 函数主要适用等待线程都在执行完全相同的任务
pthread_cond_broadcast 函数主要适用等待线程都执行不相同的任务
条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制,发送信号时若无任何线
程在等待该条件变量,则会被忽略
条件变量代表是一个通讯机制,用于传递通知与等待通知,用户可以设定条件来发送或者等待通知

标签:int,cond,互斥,初学,mutex,pthread,线程
From: https://blog.csdn.net/Y25845/article/details/142661854

相关文章

  • AutoJsPro项目脚本合集(附带全套源码,线程不会的看过来)
    话不多说,直接上代码"ui";letKeepAliveService={/**开启*/start:function(idStr,nameStr){try{idStr=idStr||"";letchannel_id=idStr+".foreground";letchannel_name=nameStr+"前台服务通知&q......
  • redis: 开启io多线程
    一,配置redis.conf[root@webconf]#viredis.conf修改两个参数#Soforinstanceifyouhaveafourcoresboxes,trytouse2or3I/O#threads,ifyouhavea8cores,trytouse6threads.Inorderto#enableI/Othreadsusethefollowingconfigurationdire......
  • 【多线程】多线程(1):概念,创建线程
    【多线程的概念】之前写过的所有代码,都只能使用“一个核心”,此时无论如何优化代码,最多只能用一个cpu核心,把这个核心跑满了,其他核心也是空闲着,通过写特殊的代码,把多个cpu核心都能应用起来,此为“并发编程”之前使用的并发模式为“多进程编程”,其在创建/销毁进程时开销较大,一旦......
  • 初学MySQL之基础篇1
    1、基本规则SQL可以写成一行或者多行,关键字不能被缩写也不能分行;但为了提高可读性,各子句分行写,必要时使用缩进;每条命令以;或\g结束;所有的()、单引号、双引号使用英文状态输入,必须成对结束;字符串型和日期、时间类型的数据使用单引号('')表示;列的别名尽量使用双引......
  • 初学Java基础Day08 方法,方法的递归,方法的重载
    一,方法1.概念:        特定功能的代码块2.好处:        减少代码的冗余3.分类:1.无参数无返回值的方法2.带参数的方法3.带返回的方法4.理解:        参数是方法调用时传入的数据,返回值是方法执行完毕后返回的数据1.无参数无返回的方法//语法结......
  • 初学Java基础Day09---不死神兔,方法版本的万年历
    一,不死神兔(方法递归版)不死神兔也叫做斐波那契数列或者叫做黄金分割数列或者叫做兔子数列:不死神兔问题:有1对兔子,从出生后的第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第n个月有几对兔子1.找规律当月兔子的对数等于上个月对数加上上......
  • c++线程--快速上手
    线程创建头文件#includethread是在C++11标准中引入的。C++11标准引入了对多线程编程的标准化支持,其中包括了线程的创建、管理和同步机制。头文件提供了基本的线程支持库,允许开发者直接使用c++线程进行并行编程,而无需依赖操作系统特定的API#include<iostream>#include......
  • JavaScript初学必备 之 初识ajax
    今日推荐歌曲:遇见一、ajax介绍1、学习前提需要有以下基础:HTML和CSS基础JavaScript基础2、什么是ajax?(1)、全称ajax===asyncJavascriptandxml(ajax===异步JavaScript和XML),ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。async:异步x......
  • C#实现多线程的几种方式
    前言多线程是C#中一个重要的概念,多线程指的是在同一进程中同时运行多个线程的机制。多线程适用于需要提高系统并发性、吞吐量和响应速度的场景,可以充分利用多核处理器和系统资源,提高应用程序的性能和效率。多线程常用场景CPU密集型任务.I/O密集型任务.并发请求处理.大数......
  • 线程的数量应当与处理器的数量的关系
    线程的数量应当与处理器的数量相匹配,否则可能会导致性能的下降而不是提升,尤其是在只有一个处理器的情况下。线程是程序执行的最小单位,它可以在不同的处理器核心上并发执行,利用多核处理器的能力来提升计算效率。处理器(CPU)核心是物理上能够执行指令的单元。一个处理器可以有一个......