首页 > 其他分享 >多线程

多线程

时间:2023-04-15 12:22:25浏览次数:45  
标签:多线程 函数 互斥 线程 pthread mutex 执行

一.多线程

1.什么是线程

要了解线程,首先需要知道进程。一个进程指的是一个正在执行的应用程序。线程对应的英文名称为“thread”,它的功能是执行应用程序中的某个具体任务,比如一段程序、一个函数等。
线程和进程之间的关系,类似于工厂和工人之间的关系,进程好比是工厂,线程就如同工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们相互配合,共同保证整个工厂的平稳运行。
每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间、文件资源等。一个进程至少包含 1 个线程,可以包含多个线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。
进程仅负责为各个线程提供所需的资源,真正执行任务的是线程,而不是进程。

2.什么是多线程

所谓多线程,即一个进程中拥有多(≥2)个线程,线程之间相互协作、共同执行一个应用程序。

当进程中仅包含 1 个执行程序指令的线程时,该线程又称“主线程”,这样的进程称为“单线程进程”。

二.多线程的相关函数

1.pthread_create() 函数

该函数用来创建线程,pthread_create() 函数声明在<pthread.h>头文件中,或者说我们接下来使用的多线程相关函数都声明在<pthread.h>头文件中。

该函数的详细使用方法可以通过CSDN技能树、菜鸟教程等地方学习,这里主要介绍学习时比较难以理解的地方。

  • 该函数的第一个参数 pthread_t *thread:传递一个 pthread_t 类型的指针变量,也可以直接传递某个 pthread_t 类型变量的地址。pthread_t 是一种用于表示线程的数据类型,每一个 pthread_t 类型的变量都可以表示一个线程。例如 int 是一种表示整数的数据类型,每个 int 类型的变量都可以表示一个整数,它们都是数据类型的一种。
  • 该函数的第三个参数是正在创建的该线程需要执行的函数,需要注意的是这里是以函数指针的方式指明新建线程需要执行的函数,该函数的形参和返回值都必须为 void* 类型。void* 类型又称空指针类型,表明指针所指数据的类型是未知的(如果不理解,类比于结构体指针一样理解)。使用此类型指针时,我们通常需要先对其进行强制类型转换,然后才能正常访问指针指向的数据。
  • 如果成功创建线程,pthread_create() 函数返回数字 0,反之返回非零值。各个非零值都对应着不同的宏,指明创建失败的原因,这里可以自己去了解。

2.pthread_exit()函数

该函数用来终止线程执行。多线程程序中,终止线程执行的方式本来有 3 种,分别是:

  • 线程执行完成后,自行终止;

  • 线程执行过程中遇到了 pthread_exit() 或者 return,也会终止执行;

  • 线程执行过程中,接收到其它线程发送的“终止执行”的信号,然后终止执行。

第一种的理解就是什么也不管,线程执行完会自己终止;第二种就是本部分要用的pthread_exit()函数,return也好理解,返回即终止;第三种方法,本来要使用pthread_cancel()函数,但在使用这个函数时会出现其他一系列的问题,解决起来非常麻烦,所以除非特殊情况,我们一般使用第二种方式。

补充:pthread_exit()和return的区别:首先,return 语句和 pthread_exit() 函数的含义不同,return 的含义是返回,它不仅可以用于线程执行的函数,普通函数也可以使用;pthread_exit() 函数的含义是线程退出,它专门用于结束某个线程的执行。实际使用中,我们终止子线程一般都使用pthread_exit()函数,不建议使用return。

3.pthread_join() 函数

该函数用来获取某个线程执行结束时返回的数据,使用也比较简单,学习一下就会使用,这里不解释。

但需要注意的有一点:一个线程执行结束的返回值只能由一个 pthread_join() 函数获取,当有多个线程调用 pthread_join() 函数获取同一个线程的执行结果时,哪个线程最先执行 pthread_join() 函数,执行结果就由那个线程获得,其它线程的 pthread_join() 函数都将执行失败。

三.线程同步

1.缘由

多线程程序中各个线程除了可以使用自己的私有资源(局部变量、函数形参等)外,还可以共享全局变量、静态变量、堆内存、打开的文件等资源。我们通常将“多个线程同时访问某一公共资源”的现象称为“线程间产生了资源竞争”或者“线程间抢夺公共资源”,线程间竞争资源往往会导致程序的运行结果出现异常,我们常常采用同步机制来解决这种问题。

2.实现方法

实现线程同步的常用方法有 4 种,分别称为互斥锁信号量条件变量读写锁

  • 互斥锁(Mutex)又称互斥量或者互斥体,是最简单也最有效地一种线程同步机制。互斥锁的用法和实际生活中的锁非常类似,当一个线程访问公共资源时,会及时地“锁上”该资源,阻止其它线程访问;访问结束后再进行“解锁”操作,将该资源让给其它线程访问。

  • 信号量又称“信号灯”,主要用于控制同时访问公共资源的线程数量,当线程数量控制在 ≤1 时,该信号量又称二元信号量,功能和互斥锁非常类似;当线程数量控制在 N(≥2)个时,该信号量又称多元信号量,指的是同一时刻最多只能有 N 个线程访问该资源。

  • 条件变量的功能类似于实际生活中的门,门有“打开”和“关闭”两种状态,分别对应条件变量的“成立”状态和“不成立”状态。当条件变量“不成立”时,任何线程都无法访问资源,只能等待条件变量成立;一旦条件变量成立,所有等待的线程都会恢复执行,访问目标资源。为了防止各个线程竞争资源,条件变量总是和互斥锁搭配使用。

  • 多线程程序中,如果大多数线程都是对公共资源执行读取操作,仅有少量的线程对公共资源进行修改,这种情况下可以使用读写锁解决线程同步问题。

这里我们使用最简单的也是最常用的方法:互斥锁。

3.互斥锁的用法

3.1互斥锁的初始化

POSIX 标准规定,用 pthread_mutex_t 类型的变量来表示一个互斥锁,该类型以结构体的形式定义在<pthread.h>头文件中。

初始化 pthread_mutex_t 变量的方式有两种,分别为:

//1、使用特定的宏
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
//2、调用初始化的函数
pthread_mutex_t myMutex;
pthread_mutex_init(&myMutex , NULL);

3.2互斥锁的“加锁”和“解锁”

对于互斥锁的“加锁”和“解锁”操作,常用的函数有以下 3 种:

int pthread_mutex_lock(pthread_mutex_t* mutex);   //实现加锁
int pthread_mutex_trylock(pthread_mutex_t* mutex);  //实现加锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);   //实现解锁

参数 mutex 表示我们要操控的互斥锁。函数执行成功时返回数字 0,否则返回非零数。

四.线程死锁

实现线程同步的 4 种方法,分别是互斥锁、信号量、条件变量和读写锁。很多初学者在使用这些方法的过程中,经常会发生“线程一直被阻塞”的情况,我们习惯将这种情况称为“死锁”。线程死锁指的是线程需要使用的公共资源一直被其它线程占用,导致该线程一直处于“阻塞”状态,无法继续执行。

使用互斥锁、信号量、条件变量和读写锁实现线程同步时,要注意以下几点:

  • 占用互斥锁的线程,执行完成前必须及时解锁;
  • 通过 sem_wait() 函数占用信号量资源的线程,执行完成前必须调用 sem_post() 函数及时释放;
  • 当线程因 pthread_cond_wait() 函数被阻塞时,一定要保证有其它线程唤醒此线程;
  • 无论线程占用的是读锁还是写锁,都必须及时解锁。

注意,函数中可以设置多种结束执行的路径,但无论线程选择哪个路径结束执行,都要保证能够将占用的资源释放掉。

避免线程死锁也有许多方法,比如最经典的银行家算法,后面会写一篇博客单独用代码实现这一算法。

五.代码演示

5.1线程的基本结构

//
// Created by 洪泽林 on 2023/4/8.
//
#include <stdio.h>
#include <pthread.h>
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void *Thread1(void *arg)                        
{
    printf("CSDN@终究还是散了\n");
    return "Thread1成功执行";
}
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void* Thread2(void* arg)
{
    printf("博客园@挽留岁月挽留你\n");
    return "Thread2成功执行";
}

int main()
{
    int res;
    pthread_t mythread1, mythread2;
    void* thread_result;
    /*创建线程
    &mythread:要创建的线程
    NULL:不修改新建线程的任何属性
    ThreadFun:新建线程要执行的任务
    NULL:不传递给 ThreadFun() 函数任何参数

    返回值 res 为 0 表示线程创建成功,反之则创建失败。
    */
    res = pthread_create(&mythread1, NULL, Thread1, NULL);
    if (res != 0) {
        printf("线程创建失败");
        return 0;
    }

    res = pthread_create(&mythread2, NULL, Thread2, NULL);
    if (res != 0) {
        printf("线程创建失败");
        return 0;
    }
    /*
    等待指定线程执行完毕
    mtThread:指定等待的线程
    &thead_result:接收 ThreadFun() 函数的返回值,或者接收 pthread_exit() 函数指定的值

    返回值 res 为 0 表示函数执行成功,反之则执行失败。
    */
    res = pthread_join(mythread1, &thread_result);
    //输出线程执行完毕后返回的数据
    printf("%s\n", (char*)thread_result);

    res = pthread_join(mythread2, &thread_result);
    printf("%s\n", (char*)thread_result);
    printf("主线程执行完毕");
    return 0;
}

5.2线程同步:卖票问题

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int ticket_sum = 10;
//创建互斥锁
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
//模拟售票员卖票
void *sell_ticket(void *arg) {
    //输出当前执行函数的线程 ID
    printf("当前线程ID:%u\n", pthread_self());
    int i;
    int islock = 0;
    for (i = 0; i < 10; i++)
    {
        //当前线程“加锁”
        islock = pthread_mutex_lock(&myMutex);
        //如果“加锁”成功,执行如下代码
        if (islock == 0) {
            //如果票数 >0 ,开始卖票
            if (ticket_sum > 0)
            {
                sleep(1);
                printf("%u 卖第 %d 张票\n", pthread_self(), 10 - ticket_sum + 1);
                ticket_sum--;
            }
            //当前线程模拟完卖票过程,执行“解锁”操作
            pthread_mutex_unlock(&myMutex);
        }
    }
    return 0;
}

int main() {
    int flag;
    int i;
    void *ans;
    //创建 4 个线程,模拟 4 个售票员
    pthread_t tids[4]={1,2,3,4};
    for (i = 0; i < 4; i++)
    {
        flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
        if (flag != 0) {
            printf("线程创建失败!");
            return 0;
        }
    }
    sleep(10);   //等待 4 个线程执行完成
    for (i = 0; i < 4; i++)
    {
        //阻塞主线程,确认 4 个线程执行完成
        flag = pthread_join(tids[i], &ans);
        if (flag != 0) {
            printf("tid=%d 等待失败!", tids[i]);
            return 0;
        }
    }
    return 0;
}

六.运行结果

ie43.jpg

jBU6.jpg

标签:多线程,函数,互斥,线程,pthread,mutex,执行
From: https://www.cnblogs.com/hong1953308269/p/17320856.html

相关文章

  • python3多线程-线程池和优先队列
    1、介绍有两种线程池方案。各线程持续存在,从任务池获取任务进行执行按照需求创建线程,每个线程只执行一个任务,结束完毕则该线程结束2、准备(1)任务池task_list任务池是用于准备各任务单元的环境,比如http爆破时的请求参数,读写文件时的路径。任务池的准备可能会占用一定时间,边准......
  • python3多线程-线程同步
    1、介绍多线程同时访问和操作同一资源,可能会造成异常。解决办法是对资源进行限制,同一时间只允许一个线程进行访问和操作。这里的资源,一般是指方法、函数或者代码块。即由多行代码组成,在逻辑上属于不可切分的操作。2、线程同步使用Thread对象的Lock和Rlock可以实现简单......
  • [计科]多进程和多线程的程序在使用上有何区别?
    区别多进程和多线程的程序在使用方式和效果上有很大的区别。多进程程序是在不同的进程之间进行协作的,每个进程都有自己独立的内存空间和系统资源。多个进程之间通过进程间通信(IPC)进行数据和信息的交换。多进程的优点在于可靠性和鲁棒性较高,一个进程出现问题不会影响到其它进程......
  • 【Linux】多线程 —— 线程概念 | 线程控制
    多线程1.线程概念1.1Linux线程与接口关系的认识1.2线程的私有&共有资源1.3线程的优缺点1.4线程的异常2.线程控制2.1pthread_create创建线程2.2pthread_join线程等待2.3线程终止的方案2.4pthrerad_detach线程分离azingneverlies正文开始@小边小边别发愁线程,是在进......
  • C#多线程学习(一) 多线程的相关概念
    C#多线程学习(一)多线程的相关概念什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。什么是线程?线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码......
  • Python爬虫之多线程加快爬取速度
    之前我们学习了动态翻页我们实现了网页的动态的分页,此时我们可以爬取所有的公开信息了,经过几十个小时的不懈努力,一共获取了16万+条数据,但是软件的效率实在是有点低了,看了下获取10万条数据的时间超过了56个小时,平均每分钟才获取30条数据。注:软件运行的环境的虚拟主机,CPU:......
  • 多线程应用案例
    需求解析一个Excel中多个sheet的数据,那么此时就可以考虑使用多线程,每个线程解析一个sheet中的数据,然后等待所有的sheet数据解析完成后,再把数据入库在这个需求中,要实现主线程等待所有现场完成shee数据解析操作,第一种方案:采用join()方法publicclassMyJoinTest{publicstaticvoid......
  • threading多线程使用
    当我们调用某段代码时需要等待一段时间后才能进行后续的操作,而这期间计算资源并未占满,这就浪费了CPU的资源和时间,此时可以采用多线程进行并行计算。如当我们使用爬虫爬取网络资源时,某个资源的爬取过程由于网络因素需要等待,而后续的资源清洗和整合等需要等待,此时可以将资源分多份......
  • java多线程 - 狂神
    多线程实现方法第一种方法:Thread自定义线程类继承Thread类重写run()方法,编写线程执行体创建线程对象,调用start()方法启动线程注意:线程不一定立即执行,由CPU安排调度继承Thread类创建多线程packagecom.waves.dxcdemo;​importjava.text.DateFormat;importja......
  • springboot整合阿里云OSS实现多线程下文件上传(aop限制文件大小和类型)
    内容涉及:springboot整合阿里云oss自定义注解及aop的使用:对上传文件格式(视频格式、图片格式)、不同类型文件进行大小限制(视频和图片各自自定义大小)线程池使用:阿里云OSS多线程上传文件阿里云OSS分片上传大文件 业务需求需求一:前端传递单个或多个小文件(这里......