多线程的使用【转载公众号:嵌入式大杂烩】

本章将分为两大部分进行讲解,前半部分将引出线程的使用场景及基本概念,通过示例代码来说明一个线程创建到退出到回收的基本流程。后半部分则会通过示例代码来说明如何控制好线程,从临界资源访问与线程的执行顺序控制上引出互斥锁、信号量的概念与使用方法。

5.1 线程的使用

5.1.1 为什么要使用多线程

在编写代码时,是否会遇到以下的场景会感觉到难以下手? 

场景一:写程序在拷贝文件时,需要一边去拷贝文件,一边去向用户展示拷贝文件的进度时,传统做法是通过每次拷贝完成结束后去更新变量,再将变量转化为进度显示出来。其中经历了拷贝->计算->显示->拷贝->计算->显示...直至拷贝结束。

这样的程序架构及其的低效,必须在单次拷贝结束后才可以刷新当前拷贝进度,若可以将进程分支,一支单独的解决拷贝问题,一支单独的解决计算刷新问题,则程序效率会提升很多。

场景二:用阻塞方式去读取数据,实时需要发送数据的时候。例如在进行串口数据传输或者网络数据传输的时候,我们往往需要双向通信,当设置读取数据为阻塞模式时候,传统的单线程只能等到数据接收来临后才能冲过阻塞,再根据逻辑进行发送。

当我们要实现随时发送、随时接收时,无法满足我们的业务需求。若可以将进程分支,一支单纯的处理接收数据逻辑,一支单纯的解决发送数据逻辑,就可以完美的实现功能。基于以上场景描述,多线程编程可以完美的解决上述问题。

5.1.2 线程概念

所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。我们可以通过多线程编程,使一个进程可以去执行多个不同的任务。

相比多进程编程而言,线程享有共享资源,即在进程中出现的全局变量,每个线程都可以去访问它,与进程共享“4G”内存空间,使得系统资源消耗减少。本章节来讨论Linux下POSIX线程。

5.1.3 线程的标识pthread_t

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

在程序中,可以通过函数,pthread_self,来返回当前线程的线程号,例程1是打印线程tid号。

获取线程号
#include <pthread.h>
pthread_t pthread_self(void);
成功:返回线程号
1  #include <pthread.h>
2  #include <stdio.h>
3 
4  int main()
5  {
6   pthread_t tid = pthread_self();//获取主线程的tid号
7   printf("tid = %lu\n",(unsigned long)tid);
8   return 0;
9  }

测试例程1:(Phtread_txex1.c)

在codeblocks运行结果如下

 

在linux结果如下

注意:

因采用POSIX线程接口,故在要编译的时候包含pthread库,使用gcc编译应gcc xxx.c -lpthread 方可编译多线程程序。

编译运行结果:

 

 

 5.1.4 线程的创建

创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
成功:返回0

在传统的程序中,一个进程只有一个线程,可以通过函数pthread_create来创建线程。

该函数第一个参数为pthread_t类型的线程号地址,当函数执行成功后会指向新建线程的线程号;

第二个参数表示了线程的属性,一般传入NULL表示默认属性;

第三个参数代表返回值为void*,形参为void*的函数指针,当线程创建成功后,会自动的执行该回调函数;

第四个参数则表示为向线程处理函数传入的参数,若不传入,可用NULL填充,有关线程传参后续小节会有详细的说明,接下来通过一个简单例程来使用该函数创建出一个线程。

测试例程2:(Phtread_txex2.c)

 1 #include <pthread.h>
 2 #include <stdio.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 
 6 void *fun(void *arg)
 7 {
 8         printf("pthread_New = %lu\n",(unsigned long)pthread_self());//打印线程的tid号
 9 }
10 
11 int main()
12 {
13 
14         pthread_t tid1;
15         int ret = pthread_create(&tid1,NULL,fun,NULL);//创建线程
16         if(ret != 0)
17         {
18                 perror("pthread_create");
19                 return -1;
20         }
21 
22         /*tid_main 为通过pthread_self获取的线程ID,tid_new通过执行pthread_create成功后tid指向的空间*/
23         printf("tid_main = %lu tid_new = %lu \n",(unsigned long)pthread_self(),(unsigned long)tid1);
24 
25         /*因线程执行顺序随机,不加sleep可能导致主线程先执行,导致进程结束,无法执行到子线程*/
26         sleep(1);
27 
28         return 0;
29 }

codeblocks运行结构如下:

 

 Linux下运行结果如下

 

 

通过pthread_create确实可以创建出来线程,主线程中执行pthread_create后的tid指向了线程号空间,与子线程通过函数pthread_self打印出来的线程号一致。

特别说明的是,当主线程伴随进程结束时,所创建出来的线程也会立即结束,不会继续执行。并且创建出来的线程的执行顺序是随机竞争的,并不能保证哪一个线程会先运行。可以将上述代码中sleep函数进行注释,观察实验现象。

去掉上述代码25行后运行结果:

 

 

posted on 2020-07-10 14:21  孙登波  阅读(198)  评论(0编辑  收藏  举报