为什么要有线程?

其实,对于任何一个进程来讲,即便我们没有主动去创建线程,进程也是默认有一个主线程的。线程是负责执行二进制指令的。进程要比线程管的宽多了,除了执行指令之外,内存、文件系统等等都要它来管。

所以,进程相当于一个项目,而线程就是为了完成项目需求,而建立的一个个开发任务。默认情况下,你可以建一个大的任务,就是完成某某功能,然后交给一个人让它从头做到尾,这就是主线程。但是有时候,你发现任务是可以拆解的,如果相关性没有非常大前后关联关系,就可以并行执行。

使用进程实现并行执行的问题有两个。
第一,创建进程占用资源太多;第二,进程之间的通信需要数据在不同的内存空间传来传去,无法共享。**

在 Linux 中,有时候我们希望将前台的任务和后台的任务分开。不同的线程可以分别完成这些任务。


一个普通线程的创建和运行过程:
image.png

线程的数据

线程可以将项目并行起来,加快进度,但是也带来的负面影响,过程并行起来了,那数据呢?

我们把线程访问的数据细分成三类。下面我们一一来看。

image.png

第一类是线程栈上的本地数据,比如函数执行过程中的局部变量。前面我们说过,函数的调用会使用栈的模型,这在线程里面是一样的。只不过每个线程都有自己的栈空间。

栈的大小可以通过命令ulimit -a查看,默认情况下线程栈大小为 8192(8MB)。我们可以使用命令ulimit -s修改。

对于线程栈,可以通过下面这个函数 pthread_attr_t,修改线程栈的大小。

  1. int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

主线程在内存中有一个栈空间,其他线程栈也拥有独立的栈空间。为了避免线程之间的栈空间踩踏,线程栈之间还会有小块区域,用来隔离保护各自的栈空间。一旦另一个线程踏入到这个隔离区,就会引发段错误。

第二类数据就是在整个**进程**里共享的全局数据。例如全局变量,虽然在不同进程中是隔离的,但是在一个进程中是共享的。

这就是第三类数据,线程私有数据(Thread Specific Data),可以通过以下函数创建:

  1. int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))

可以看到,创建一个 key,伴随着一个析构函数。

key 一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往 key 中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。
**
我们可以通过下面的函数设置 key 对应的 value。

  1. int pthread_setspecific(pthread_key_t key, const void *value)

我们还可以通过下面的函数获取 key 对应的 value。

  1. void *pthread_getspecific(pthread_key_t key)

而等到线程退出的时候,就会调用析构函数释放 value

数据的保护

1、Mutex,全称 Mutual Exclusion,中文叫互斥。顾名思义,有你没我,有我没你。它的模式就是在共享数据访问的时候,去申请加把锁,谁先拿到锁,谁就拿到了访问权限

2、条件变量,也就是说如果没事儿,就让大家歇着,有事儿了就去通知。但是当它接到了通知,来操作共享资源的时候,还是需要抢互斥锁,因为可能很多人都受到了通知,都来访问了,所以条件变量和互斥锁是配合使用的

Mutex 的使用流程
image.png

条件变量工作模式如下图

image.png


image.png