Posix在线文档:
The Single UNIX Specification, Version 2 (opengroup.org)
本文主要参考这位大神的文章:
Posix多线程编程学习笔记 - 凌峰布衣 - 博客园 (cnblogs.com)
线程安全问题
多线程编程中,经常遇到的就是线程安全问题,或者说要考虑线程同步的问题。
线程同步这个概念其实挺抽象的。关于线程同步,是指各线程执行时需要按照一定的先后顺序,而不是各线程自己想干嘛就干嘛。
本人之前对线程同步,以及主要的同步技术如互斥锁、信号量、条件变量等没怎么搞透彻,这里再想一想看看。
考虑以下三个场景:
- 场景1:多个线程,改变了同一个全局变量;
- 场景2:多个线程,都调用printf来打印输出;
- 场景3:多个线程,其中A线程必须等B线程执行结束才能执行。
针对场景1,可以使用互斥锁,保证同一时间只有一个线程来操作全局变量,本质是什么呢?本质是因为修改变量的过程并不是原子操作,而是会被打断的,所以,需要在修改变量期间,保证不会被其他线程打断;
针对场景2,我们需要的其实也是printf这个过程不会被打断,要不然就是这个线程打印一会儿,那个线程打印一会儿,输出的内容都串在一起了,这种情况下怎么处理呢?在freertos中,不想某段操作被其他任务打断,可以使用临界区函数来实现,那么Linux中呢?其实可以用信号量,想一想,信号量其实主要就是统计某个资源的使用情况,printf其实就是一个输出的资源,我们可以用信号量来实现,每次只有一个线程可以获取该资源;
针对这两个场景再想想,printf是个资源,其实一个全局变量也是个资源,是不是也可以用信号量呢?另外,printf要想不被打断,是不是也可以使用互斥锁呢?理论上都是可以互换的。
二者本质上都是为了实现原子操作。
再来看看第三个场景,各线程有先后执行顺序,就可以使用条件变量,这一点,相对前面两个场景,没那么容易搞混。
而这三个技术,互斥锁、信号量以及条件变量,就是多线程编程中,实现线程同步的主要手段。再看看线程同步这个词,意思其实就是我这个线程操作资源时,你其他线程得等着,这就是同步的含义。使用线程同步技术,从而保证线程安全。
这几个技术的本质应该是类似的,只是侧重点不一样,需要在实际开发中慢慢体会。
互斥锁和信号量的区别?
参考:
信号量和互斥量(锁)的区别_信号量和互斥锁的区别-CSDN博客
为了更好地区分应用场景,再想想。
互斥锁主要是用在某个会被多线程调用的代码段中;
而信号量,是先有个信号量,然后这个线程释放信号量,另一个线程获取信号量,其实也可以控制线程实现的先后顺序。
根据《UNIX环境高级编程》,Pthread处理线程同步的,主要是互斥锁和条件变量,而信号量在进程间通信才被提及,貌似信号量主要是用于多进程对共享数据对象的访问。
注意Linux中的相关概念和RTOS中的相关概念的区别。
Linux中会有多个进程,相对复杂,RTOS中一般只有一个进程,也就是我们的应用,然后里面有各个子任务,对应的是Linux中的线程的概念。
有哪些不一样呢?比如RTOS中消息队列就是在各线程任务之间传递消息,而Linux中主要是用在进程间通信;又比如RTOS中的信号量分为好几种,比如二值信号量、计数信号量、互斥信号量等等,都用在任务间的同步,而Linux中,这些技术有的是用于线程的,有的是用于进程的;再比如RTOS中有临界区保护这一技术手段,不过Linux中一般就是通过互斥锁信号量等等实现。
总之要明确的是,RTOS中一般只有线程的概念,也就是各子任务,所以各技术,都是用在任务之间;但是Linux中,既有线程又有进程,不同的技术有不同的应用场景,所以要加以区分。 另外,不同的操作系统,可能略有差别,实际使用时,需要进一步了解,不过用法都大差不差,重要的是,别以为都是统一的用法,然后产生疑惑,还不知道是咋回事。
Pthreads 概述
历史上,硬件销售商实现了私有版本的多线程库。这些实现在本质上各自不同,使得程序员难于开发可移植的应用程序。 为了使用线程所提供的强大优点,需要一个标准的程序接口。对于UNIX系统,IEEE POSIX 1003.1c(1995)标准制订了这一标准接口。依赖于该标准的实现就称为POSIX threads 或者Pthreads。现在多数硬件销售商也提供Pthreads,附加于私有的API。 Pthreads 被定义为一些C语言类型和函数调用,用pthread.h头(包含)文件和线程库实现。
所以说,Pthreads,其实就是POSIX threads,也就是符合posix标准的线程操作接口。
线程管理
Pthreads API中的函数可以非正式的划分为三大类:
线程管理(Thread management): 第一类函数直接用于线程:创建(creating),分离(detaching),连接(joining)等等。包含了用于设置和查询线程属性(可连接,调度属性等)的函数。
互斥量(Mutexes): 第二类函数是用于线程同步的,称为互斥量(mutexes),是"mutual exclusion"的缩写。Mutex函数提供了创建,销毁,锁定和解锁互斥量的功能。同时还包括了一些用于设定或修改互斥量属性的函数。
条件变量(Condition variables):第三类函数处理共享一个互斥量的线程间的通信,基于程序员指定的条件。这类函数包括指定的条件变量的创建,销毁,等待和受信(signal)。设置查询条件变量属性的函数也包含其中。
创建和结束线程
函数:
pthread_create (thread,attr,start_routine,arg)
pthread_exit (status)
pthread_attr_init (attr)
pthread_attr_destroy (attr)
最初,main函数包含了一个缺省的线程,也就是主线程。其它线程则需要程序员显式地创建。
有个疑惑,主线程mian里面创建完其他线程后,是进入while死循环,还是直接return返回?
参考:
线程创建后,避免主线程先于子线程结束的四种方式_怎么让新创建的线程不随主线程结束运行-CSDN博客
pthread_create 创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用。
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void*), void *arg);
pthread_create参数:
- thread:返回一个新线程标识符,也就是线程ID
- attr:线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值,一般需要时会在属性里设置线程的优先级和栈大小;
- start_routine:线程将会执行的函数,注意该函数的类型void *(*)(void *)
- arg: 传递给start_routine单个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。
一个进程可以创建的线程最大数量取决于系统实现。
Q:一个线程被创建后,怎么知道操作系统何时调度该线程使之运行?
A:线程何时何地被执行取决于操作系统实现,强壮的程序应该不依赖于线程执行的顺序。
线程属性:
- 线程被创建时会带有默认的属性。其中的一些属性可以被程序员用线程属性对象来修改;
- pthread_attr_init 和 pthread_attr_destroy用于初始化/销毁先成属性对象;
结束终止:
- 结束线程的方法有以下几种:
- 线程从主线程(main函数的初始线程)返回;
- 线程调用了pthread_exit函数;
- 其它线程使用 pthread_cancel函数结束线程;
- 调用exec或者exit函数,整个进程结束;
pthread_exit用于显式退出线程。典型地,pthread_exit()函数在线程完成工作不再需要的时候被调用,退出线程。注意,哪个线程调用的,就结束哪个线程。
#include <pthread.h>void pthread_exit(void *value_ptr);
参数retval 是 void* 类型的指针,可以指向任何类型的数据,它指向的数据将作为线程退出时的返回值。如果线程不需要返回任何数据,将 retval 参数置为 NULL 即可。
示例如下:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h>void *thread_fun1(void *param) {while(1){printf("I am thread-1\n");sleep(1);}return NULL; }void *thread_fun2(void *param) {while(1){printf("you are thread-2\n");sleep(1);}return NULL; }int main(int argc, char *argv[]) {pthread_t tid1, tid2;int rc;rc = pthread_create(&tid1, NULL, thread_fun1, NULL);if (rc < 0){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } rc = pthread_create(&tid2, NULL, thread_fun2, NULL);if (rc < 0){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); }pthread_exit(NULL);//主线程退出了 }
互斥量
互斥量(Mutex)是“mutual exclusion”的缩写。互斥量是实现线程同步,和保护同时写共享数据的主要方法。
互斥量对共享数据的保护就像一把锁。在Pthreads中,任何时候仅有一个线程可以锁定互斥量,因此,当多个线程尝试去锁定该互斥量时仅有一个会成功。直到锁定互斥量的线程解锁互斥量后,其他线程才可以去锁定互斥量。线程必须轮着访问受保护数据。
互斥量可以防止“竞争”条件。
一个拥有互斥量的线程经常用于更新全局变量。确保了多个线程更新同样的变量以安全的方式运行,最终的结果和一个线程处理的结果是相同的(在受保护的地方,多个线程对于资源的操作,就像是一个线程中对数据的先后处理)。这个更新的变量属于一个“临界区(critical section)”。
使用互斥量的典型顺序如下:
- 创建和初始一个互斥量
- 多个线程尝试去锁定该互斥量
- 仅有一个线程可以成功锁定改互斥量
- 锁定成功的线程做一些处理
- 线程解锁该互斥量
- 另外一个线程获得互斥量,重复上述过程
- 最后销毁互斥量
当多个线程竞争同一个互斥量时,失败的线程会阻塞在lock调用处。可以用“trylock”替换“lock”,则失败时不会阻塞。
当保护共享数据时,程序员有责任去确认是否需要使用互斥量。如,若四个线程会更新同样的数据,但仅有一个线程用了互斥量,则数据可能会损坏。
如下程序:
补充
编译时报错:
undefined reference to 'pthread_create'
参考:
Linux下undefined reference to ‘pthread_create’问题解决-CSDN博客
问题原因:
pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
问题解决:
在编译中要加 -lpthread参数
gcc thread.c -o thread -lpthread
thread.c为你些的源文件,不要忘了加上头文件#include<pthread.h>