原文 <a href="https://blog.csdn.net/qq_41291253/article/details/92065354">https://blog.csdn.net/qq_41291253/article/details/92065354</a>
一、什么是多线程?
再说多线程之前引入多进程的概念。那什么是进程?在 Windows 系统中,我们每开一个应用程序系统就为其开辟一个进程,就比如我们打开一个 Word 文档就是一个进程,如果再此基础上按 control + N 在新建个 Word 文档这就开了两个进程。
其中每个进程的内存空间都有保存全局变量的“数据区”、像 malloc / new 等函数的动态分配提供空间的堆(Heap)、函数运行时使用的栈(Stack)构成。每个进程都拥有这样的独立空间,多个进程的内存结构可以参考下图。
但如果以获得多个代码执行流为主要目的,就不行该这样分离内存结构,而只需要分离栈区域。这样可以有如下优点:
上下文切换时(这里指进程间的切换)不需要切换数据区和堆
可以利用数据区和堆交换数据
实现以上目地的方法就是多线程,就像我们打开一个 Word 文档,在里面同时编辑 sheet1,sheet2 一样,每一个 sheet 就是一个线程。线程为了保持多条代码执行流而隔离开了栈区域,因此具有如下图的结构:
二、为什么要创建线程
通过上面的讲解我们知道了,多线程是能够在一个应用程序中进行多个任务。比如我们要打印 1000 页的Word,如果只有一个线程,那么我们在打印结束前是不可以对 Word 进行操作的,而且打印1000 页要耗费很多时间。但是,实际并不是如此,我们在打印的时候依然可以对 Word 进行编辑操作,这就是多线程的一种应用,处理耗时程序。同样的应用还用在数据采集当中。
三、线程之间如何通信
在 Windows 系统中线程之间的通信有两种方式
使用全局变量进行通信
使用自定义消息进行通信
在第一部分中我们介绍了,线程的数据区和堆区域是共享的,所以我们可以声明全局变量来进行线程之间的通信和数据交换。如果线程之间传递的数据比较复杂,我们可以定义一个结构,通过传递指向该结构的指针进行消息传递。接着让线程监视这个变量,当这个变量符合一定的条件时,表示该线程终止。
使用自定义消息暂时不做解释,是 Windows 编程中MFC 的内容,如果有读者和我同样学习 MFC 请在文章下面留言,我在补充相关内容。下面给出基于 Linux 系统的代码。
#include <stdio.h>
#include <pthread.h>
void *thread_summation(void *arg); //声明线程
int sum = 0; //线程间通信用的全局变量
int main(int argc, const char * argv[])
{
pthread_t id_t1, id_t2;
int range1[] = {1, 5};
int range2[] = {6, 10};
pthread_create(&id_t1, nullptr, thread_summation, (void*)range1); //创建线程
pthread_create(&id_t2, nullptr, thread_summation, (void*)range2);
pthread_join(id_t1, NULL); //控制线程的执行流
//调用该函数的线程将进入等待状态,直到第一个参数 ID 的线程终止为止。
pthread_join(id_t2, NULL);
return 0;
}
void *thread_summation(void *arg)
{
int start = ((int*)arg)[0];
int end = ((int*)arg)[1];
while(start <= end)
{
sum += start; //这里设计到对 sum 值的访问
start++;
}
return NULL;
}
流程图如下所示:
四、线程安全
在第三部分我已经提出了示例中存在的临界区问题,该问题的发生是有概率的,和电脑系统配置有关,运行结果可能因机器而异。那么怎么产生的这个问题呢?
上述示例中两个线程同时访问变量 sum,这里的访问指的是对 sum 的值的更改。除此之外例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现上面的错误。再比如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户需求,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。具体解释涉及到 CPU 和内存管理,再此略,如果感兴趣的读者,可以自行查找相关文献书籍。
那么怎么解决这个问题呢?
线程间的同步就可以解决这个问题。
五、线程的同步
使隶属于同一进程的线程协调一致的工作就是线程间的同步。在多线程环境里,需要对线程进行同步。常用的同步对象有临界区(Critical Section)、互斥(Mutex)、信号量(Semaphore)和事件(event)等。用于解决线程访问顺序引发的问题。需要同步的情况可以从以下两方面考虑:
同时访问同一内存空间时发生的情况。
需要指定访问同一内存空间的线程执行顺序的情况。
支持多线程同步的同步类
类型 说明
Critical Section 当在一个时间内仅有一个线程被允许修改数据或其某些其他控制资源时使用,用于保护共享资源(比如写数据)
Mutex 当多个应用(多个进程)同时存取相应资源时使用,用于保护共享资源
Semaphore 一个应用允许同时有多个线程访问相应资源时使用(比如读数据),主要功能用于资源计数
event 某个线程必须等待某些事件发生后才能存取相应资源时使用,以协调线程之间的动作。
有疑问加站长微信联系(非本文作者)