linux 线程与进程 -2-多线程应用编程

2023-05-16

    * 多线程的管理

    * 多线程的互斥锁和条件变量的使用!

什么是进程
进程(process )是一个已经开始执行但还没终止的程序实例。
Linux系统下使用ps 命令可以查看到当前正在执行的进程。每个进程包含有进程运行环境、内存地址空间进程ID、和至少一个被称为线程的执行控制流等资源。同一个程序可以实例化为多个进程实体。操作系统中所有进程实体共享着计算机系统的 CPU、外设等资源。

什么是线程
线程(thread)是包含在进程内部的顺序执行流,是进程中的实际运作单位,也是操作系统能够进行调度的最小单位。一个进程中可以并发多条线程,每条线程并行执行不同的任务。
所以进程和线程的关系如下:

线程与进程的关系
线程与进程的关系可以归结为以下几点:
  一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个主线程;
  资源分配给进程,同一进程的所有线程共享该进程的所有资源;
  线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
  进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源;
  在创建或撤销进程时,由于系统要为之分配和回收资源,导致系统的开销大于创建或撤销线程时的开销。

1.多线程

为什么要使用多线程?
多进程程序结构和多线程程序结构有很大的不同,多线程程序结构相对于多进程程序结构有以下的优势:
(1 )方便的通信和数据交换
线程间有方便的通信和数据交换机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
(2 )更 高 效的利用 CPU
使用多线程可以加快应用程序的响应。这对图形界面的程序尤其有意义,假如一个操作耗时很长,那么整个系统都会等它操作,此时程序不会响应键盘、鼠标、菜单等操作,而使用多线程技术,将耗时长的操作置于一个新的线程,就可以避免这种尴尬情况的发生。同时多线程使多 CPU 系统更加有效。操作系统会保证当线程数不大于 CPU 数目时,不同的线程运行于不同的 CPU 上。

2.POSIX Threads(通常简称为 Pthreads)

Pthreads定义了创建和操纵线程的一套 API 接口,一般用于 Unix-like POSIX 系统中(如 FreeBSD、GNU/Linux、OpenBSD、Mac OS 等系统)。
Pthreads 接口可以根据功能划分四个组:
  线程管理
  互斥量
  条件变量
  同步
(1)线程的应用和编译:
编写 Pthreads 多线程的程序时,源码只需要包含 pthread.h 头文件就可以使用Pthreads库中的所有类型及函数:
  #include <pthread.h>
在编译 Pthread 程序时在编译和链接过程中需要加上-pthread 参数:
  LDFLAGS += -pthread
(2)线程的管理--对id的创、终、等、分、设的管理操作:
1/5).创建线程(线程被创建后将立即运行)
       在进程中创建一个新线程的函数是 pthread_create(),原型如下:
                int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数说明:
  thread 用来指向新创建线程的 ID;
  attr 用来表示一个封装了线程各种属性的属性对象,如果 attr为NULL,新线程就使用默认的属性;
  start_routine 是线程开始执行的时候调用的函数的名字,start_routine 函数有一个指向 void 的指针参数,并有 pthread_create 的第四个参数 arg 指定值,同时 start_routine函数返回一个指向 void 的指针,这个返回值被 pthread_join 当做退出状态处理;
  arg 为参数 start_routine 指定函数的参数。

2/5).终止线程(区分终止线程和终止进程的差别)
   终止线程:如果主线程在创建了其它线程后没有任务需要处理,那么它应该阻塞等待直到所有线程都结束为止,或者应该调用 pthread_exit(NULL)。
   [   终止进程:进程的终止可以通过直接调用 exit()、执行 main()中的 return、或者通过进程的某个其它线程调用 exit()来实现。在以上任何一种情况发生时,所有的线程都会被终止。]
调用 exit()函数会使整个进程终止,而调用pthread_exit()只会使得调用线程终止,同时在创建的线程的顶层执行 return 线程会隐式地调用 pthread_exit()。pthread_exit()函数原型如下:void pthread_exit(void *retval);
      retval 是一个 void 类型的指针,可以将线程的返回值当作 pthread_exit()的参数传入,这个值同样被 pthread_join()当作退出状态处理。如果进程的最后一个线程调用了 pthread_exit(),进程会带着状态返回值 0 退出;

3/5).线程的分离与连接-线程可以分为 分离线程(DETACHED)和 非分离线程(JOINABLE)两种:
  分离线程是退出时会释放它的资源的线程;
  非分离线程退出后不会立即释放资源,需要另一个线程为它调用 pthread_join函数或者进程退出时才会释放资源。
            只有非分离线程才是可连接的,分离线程退出时不会报告它的退出状态。
  a.线程分离-pthread_detach()函数可以将非分离线程设置为分离线程,函数原型如下
            int pthread_detach(pthread_t thread);//参数 thread 是要分离的线程的 ID。线程可以自己来设置分离,也可以由其它线程来设置分离,以下代码线程可设置自身分离:
              pthread_detach( pthread_self() );//成功返回 0;失败返回一个非 0 的错误码
  b.线程连接-如果一个线程是非分离线程,那么其它线程可调用 pthread_join()函数对非分离线程进行连接
pthread_join()函数原型如下:int pthread_join(pthread_t thread, void **retval);
pthread_join()函数将调用线程挂起,直到参数 thread 指定的目标线程终止运行为止。
参数 retval 的作用是为指向线程的返回值的指针提供一个位置,这个返回值是目标线程调用 pthread_exit()或者 return 后所返回的值。当目标线程无需返回时可使用 NULL 值,调用线程如果不需对目标线程的返回状态进行检查可直接将 retval 赋值为NULL。
            如果 pthread_join()//成功调用,它将返回 0 值,如果不成功,pthread_join()返回一个非 0的错误码;

           为了防止内存泄露,长时间运行的程序最终应该为每个线程调用 pthread_detach()或者被 pthread_join !!!

4/5).设置操作线程的属性、状态和线程栈
    属性对象
(1 )初始化属性对象-pthread_attr_init()函数用于将属性对象使用默认值进行初始化,函数原型如下:
int pthread_attr_init(pthread_attr_t *attr);//函数只有一个参数,是一个指向 pthread_attr_t 的属性对象的指针。成功返回 0,否则返回一个非 0 的错误码。
(2 ) 销毁属性对象-销毁属性对象使用 pthread_attr_destroy()函数,函数原型如下:
int pthread_attr_destroy(pthread_attr_t *attr);//函数只有一个参数,是一个指向 pthread_attr_t 的属性对象的指针。成功返回 0,否则返回一个非 0 的错误码。
  线程状态-线程可以有两种状态,分别是:
  PTHREAD_CREATE_JOINABLE——非分离线程;
  PTHREAD_CREATE_DETACHED——分离线程。
(3 )获取线程状态-获取线程状态的函数是 pthread_attr_getdetachstate(),原型如下:
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);//参数 attr 是一个指向已初始化的属性对象的指针,detachstate 是所获取状态值的指针。成功返回 0,否则返回一个非 0 的错误码。
(4 )设置线程状态--设置线程状态的函数是 pthread_attr_setdetachstate(),原型如下:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//参数 attr 是一个指向已初始化的属性对象的指针, detachstate 是要设置的值。成功返回0,否则返回一个非 0 的错误码。
    线程栈
每个线程都有一个独立的调用栈,线程的栈大小在线程创建的时候就已经固定下来,Linux 系统线程的默认栈大小为 8MB,只有主线程的栈大小会在运行过程中自动增长。用户可以通过属性对象来设置和获取栈大小。
(5 )获取线程栈-获取线程栈大小的函数是 pthread_attr_getstacksize(),原型如下:
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);//参数 attr 是一个指向已初始化的属性对象的指针,stacksize 是保存所获取栈大小的指针。成功返回 0,否则返回一个非 0 的错误码。
(6 ) 设置线程栈--设置线程栈大小的函数是 pthread_attr_setstacksize(),原型如下:
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);//参数 attr 是一个指向已初始化的属性对象的指针,stacksize 是需要设置的栈大小。成功返回 0,否则返回一个非 0 的错误码。

5/5)等待-阻塞等待和非阻塞等待

3.创建互斥量
pthreads 使用 pthread_mutex_t 类型的变量来表示互斥量,同时在使用互斥量进行同步前
需要先对它进行初始化,可以用静态或动态的方式对互斥量进行初始化。
(1 )静态初始化-对于静态分配的 pthread_mutex_t 变量来说,只要将 PTHREAD_MUTEX_INITIALIZER赋给变量就行了。
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2 )动态初始化-对于动态分配或者不使用默认属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作。pthread_mutex_int()函数原型如下:
        int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数 mutex 是一个指向要初始化的互斥量的指针;参数 attr 传递 NULL 来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。函数成功返回 0,否则返回一个非 0 的错误码;

   静态初始化程序通常比调用 pthread_mutex_init 更有效,而且在任何线程开始执行之前,确保变量被初始化一次!!!

3.线程互斥资源的锁定

(1).创建互斥量-pthreads 使用 pthread_mutex_t 类型的变量来表示互斥量,同时在使用互斥量进行同步前
需要先对它进行初始化,可以用静态或动态的方式对互斥量进行初始化。
1 )静态初始化-对于静态分配的 pthread_mutex_t 变量来说,只要将 PTHREAD_MUTEX_INITIALIZER赋给变量就行了。
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2 )动态初始化-对于动态分配或者不使用默认属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作。pthread_mutex_int()函数原型如下:
        int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数 mutex 是一个指向要初始化的互斥量的指针;参数 attr 传递 NULL 来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。函数成功返回 0,否则返回一个非 0 的错误码
(2).销毁互斥量使用 pthread_mutex_destroy()函数,原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//----eg.动态创建锁------
           int error;
          pthread_mutex_t mylock;
         if (error = pthread_mutex_destroy(&mylock))
         fprintf(stderr, "Failed to destroy mylock : %s\n", strerror(error));
//----eg.销毁锁-----
        int error;
        pthread_mutex_t mylock;
       if (error = pthread_mutex_init(&mylock, NULL))
        fprintf(stderr, "Failed to initialize mylock : %s\n", strerror(error));
//--------
(3).加锁与解锁
1)加锁-线程试图锁定互斥量的过程称之为加锁。
pthreads 中有两个试图锁定互斥量的函数,pthread_mutex_lock()和 pthread_mutex_
trylock()。pthread_mutex_lock()函数会一直阻塞到互斥量可用为止,而 pthread_mutex_trylock()
则尝试加锁,但通常会立即返回。函数原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数 mutex 是需要加锁的互斥量。函数成功返回 0,否则返回一个非 0 的错误码,其中
在另一个线程已持有锁的情况下,调用 pthread_mutex_trylock()函数时错误码为 EBUSY。
2)解锁-解锁是线程将互斥量由锁定状态变为解锁状态。
pthread_mutex_unlock()函数用来释放指定的互斥量。函数原型如下:int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数 mutex 是需要解锁的互斥量。函数成功返回 0,否则返回一个非 0 的错误码。
只有在线程进入临界区之前正确地获取了适当的互斥量,才能在离开临界区时释放互斥
量。以下伪代码展示了互斥量保护临界区的基本用法:
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mylock);
     ....临界区代码.....
pthread_mutex_unlock(&mylock);
(4.)死锁和避免
1).什么是死锁  ?
死锁是指两个或两个以上的执行序列在执行过程中,因争夺资源而造成的一种互相等待的现象。例如:一个线程 T1 已锁定了一个资源 R1,又想去锁定资源 R2,而此时另一个线程 T2 已锁定了资源 R2,却想去锁定资源 R1。这两个线程都想得到对方的资源,而又不愿释放自己的资源,结果就是两个线程都在等待而无法执行
2).避免死锁的方式
当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生,如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。例如,规定程序内有三个互斥锁的加锁顺序为 mutexA->mutexB->mutexC,则线程 t1、t2、t3 线程操作伪代码如下所示:(注意,获取锁的方式在锁的队列中时使用的阻塞获取,所以一个时刻只有一个线程拿到一个互斥锁,
t1                             t2                t3
lock(mutexA)   lock(mutexA)    lock(mutexB)
lock(mutexB)   lock(mutexC)    lock(mutexC)
lock(mutexC)

顺序排列抑制就可以避免死锁的情况!

4.互斥锁的高级使用方式--条件变量

目的:为已解决某情况下,互斥锁带来的资源浪费(缩减CPU 处理时间)和效率降低(提高线程的处理效率)
                                     创建与销毁
(1).创建条件变量 -
Pthreads 用 pthread_cond_t 类型的变量来表示条件变量。程序必须在使用 pthread_cond_t变量之前对其进行初始化。
1 ) 静态初始化-
对于静态分配的变量可以简单地将 PTHREAD_COND_INITIALIZER 赋值给变量来初始化默认行为的条件变量。
                                     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 是一个指向需要初始化 pthread_cond_t 变量的指针,参数 attr 传递 NULL 值时,pthread_cond_init()将 cond 初始化为默认属性的条件变量。函数成功将返回 0;否则返回一个非 0 的错误码。
静态初始化程序通常比调用 pthread_cond_init()更有效,而且在任何线程开始执行之前,确保变量被执行一次。以下代码示例了条件变量的初始化。
pthread_cond_t cond;
int error;
if (error = pthread_cond_init(&cond, NULL));
fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));
(2).销毁条件变量-函数 pthread_cond_destroy()用来销毁它参数所指出的条件变量,函数原型如下:
                                           int pthread_cond_destroy(pthread_cond_t *cond);
函数成功调用返回 0,否则返回一个非 0 的错误码。以下代码演示了如何销毁一个条件变量。
pthread_cond_t cond;
int error;
if (error = pthread_cond_destroy(&cond))
fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));
(3).等待-
条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。条件等待函数有 pthread_cond_wait()pthread_cond_timedwait()和两个,函数原型如下:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
pthread_cond_wait()函数在条件不满足时将一直等待,而 pthread_cond_timedwait()将只等待一段时间。
参数 cond 是一个指向条件变量的指针,参数 mutex 是一个指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量。pthread_timedwait()的第三个参数 abstime 是一个指向返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出,abstime 是个绝对时间,而不是时间间隔。
以上函数成功调用返回 0,否则返回非 0 的错误码,其中 pthread_cond_timedwait()函数如果 abstime 指定的时间到期,错误码为 ETIMEOUT。
以下代码使得线程进入等待,直到收到通知并且满足 a 大于等于 b 的条件。
pthread_mutex_lock(&mutex)
while(a < b)
pthread_cond_wait(&cond, &mutex)
pthread_mutex_unlock(&mutex)
(4).通知
当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。
条件通知函数有 pthread_cond_signal()和 pthread_cond_broadcast()函数,其中 pthread_cond_signal 函数可以唤醒一个在条件变量等待队列等待的线程,而 pthread_cond_broadcast函数可以所有在条件变量等待队列等待的线程。函数原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
参数 cond 是一个指向条件变量的指针。函数成功返回 0,否则返回一个非 0 的错误码。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux 线程与进程 -2-多线程应用编程 的相关文章

随机推荐

  • 华为部分在线测试题

    huaweiDemo2 cpp 定义控制台应用程序的入口点 xff1a 平台 VS2017 xff0c 2019 01 01 1 问题 xff1a 就算串长度 输入一个字符串 xff0c 用指针求出字符串的长度 include 34 std
  • 从地址和存储器角度重新认识微控制器,分析堆栈 分析代码

    代码分析基础 xff1a 从底层去分析代码的内存管理 和设备资源 1 存储器器和地址 1 xff09 存储器 存储器是计算机结构的重要组成部分 存储器是用来存储程序代码和数据的部件 xff0c 有了存储器计算机才具有记忆功能 基本的存储器种
  • 智能卡操作(非接触CPU卡又称智能卡) 总结

    1 数据传输格式和初始化 对于刚接触智能卡的工程师来说 xff0c 在阅读7816 3规范的时候 xff0c 常常被其中的一些术语迷惑 xff0c 读起来会觉得有些别扭 尤其是在看到复位应答中的F和D设置以及对应的etu的时候 xff0c
  • 国密PSAM卡与CPU(用户卡)操作过程 小结

    1 终端 43 PSAM 43 CPU用户卡 常规的操作过程 PSAM 卡是作为秘密密钥的载体 xff0c 专门执行加密和数字签名等任务 xff1b 所有它常常跟一个MCU或者终端设备组在一起构成一个整体 xff1b CPU用户卡作为一个从
  • maven仓库更换为阿里的仓库同时更换插件和依赖

    span class token generics function span class token punctuation lt span repositories span class token punctuation gt spa
  • 5.8G公路协议开发 小结1

    5 8G高速公路协议开发 小结 1 5 8G高速公路协议 xff08 1 xff09 高速公路的标识点天线协议 xff08 简称自由流协议 自由流收费指的是在行车的过程中不知不觉就收取了一定的费用 在收费点的时候我们再也不用停车等待缴费 x
  • BUG小结之-网络编程的通讯问题回顾

    问题 代码和驱动确定无误 xff0c 但是无法正常收发信息 xff1b 现象 xff1a 1 UDP可以正常收数据 xff08 经过打印验证过了 xff09 xff0c 但是无法发回网口助手 xff1b 2 网络调试助手无法自动寻找和映射i
  • bug-make绝对路径相对路径-flage

    绝对路径变相对路径
  • linux网络通讯的虚拟IP的开发设计(双IP的开发)

    虚拟IP 即在一个物理IP xff08 比如这个IP为192 168 2 29 xff09 的基础上 xff0c 绑定其他的IP xff08 比如192 168 1 xxx xff09 xff0c 用windows的话说 xff0c 就是为
  • linux UDP通讯的地址选择-flag

    单播和广播的设定 UDP通讯总结可以参考链接 https blog csdn net xiaoxilang article details 80839797
  • linux-Ubuntu安装后续工作小结-flag

    flage目的 xff1a 1 由于操作不熟练 xff0c 被某人鄙视了一次 2 常温习 xff0c 防遗忘 xff0c 快速解决 xff0c 提高效率 1 安装Ubuntu xff08 1 xff09 制作ubuntu启动U盘 制作用ru
  • RemoteFX usb设备重定向-USB虚拟机重映射

    1 usb设备重定向 实现的效果 xff0c 如图1 各种usb 串口 视频usb 打印机 xff01 xff01 如果你参考我的资料 xff0c 依然不能解决问题 xff0c 可以参考博客 xff1a https yq aliyun co
  • shell编程、ssh-root量产、ping-IP脚本操作 问题-flag

    1 su root Authentication failure问题的解决 su su root 命令不能切换root xff0c 提示su Authentication failure 只要你sudo passwd root过一次之后 x
  • linux编译器的下关于gcc、g++、make和CMake几个概念的区别

    1 什么是gcc g 43 43 首先说明 xff1a gcc 和 GCC 是两个不同的东西 GCC GNU Compiler Collection GUN编译器集合 xff0c 它可以编译C C 43 43 JAV Fortran Pas
  • linux-vim编辑工具的基础的常用操作 小结

    1 设置行号 xff08 临时设置和永久性设置 xff09 临时设置 如果要显示该文件的所有行号 xff0c 则需要在vim的命令模式下输入 set nu 设置显示行号 xff0c 等同setnumber set nonu xff08 取消
  • 内网离线安装vscode插件

    一 官网下载vscode安装包 xff08 https code visualstudio com xff09 下载之后将安装包拷贝到需要的电脑进行安装并创建桌面快捷方式 二 vscode插件官网 xff08 https marketpla
  • usb/uart转网口模块选型设计 小结

    选型原则 通讯速率尽量可能快 xff0c 工作温度必须是工业级的 xff0c 成本尽量可能低 xff1b 保证用户可操作空间较大 1usb转网口 AX88772BLI xff0c ZLAN1003 xff0c IP101GRI xff08
  • Git bash基础操作指令 小结

    1 常用git指令 git的安装配置 xff08 只有初次使用需要配置 xff09 用户名和邮件 git config global user name 34 noxue 34 git config global user email ad
  • linux-ARM开发板--嵌入式开发平台-选型

    最近有一个项目以前一直在用工控机实现 xff0c 现在需要优化功能 缩减成本 xff0c 故有寻找linux ARM开发板的需求 xff1b 后期有很大可能还会自己会画PCB板 内核裁剪等设计的需求 xff1b 1 根据需求 xff0c 限
  • linux 线程与进程 -2-多线程应用编程

    多线程的管理 多线程的互斥锁和条件变量的使用 xff01 什么是进程 进程 xff08 process xff09 是一个已经开始执行但还没终止的程序实例 Linux系统下使用ps 命令可以查看到当前正在执行的进程 每个进程包含有进程运行环