linux多线程

多线程:所有程序并发运行,多条路径可以同时执行
多线程包括:理解线程并发、线程同步方法、线程安全概念
线程指:进程内部得一条执行路径(从主函数的第一行代码到最后一行)

主线程:main         子线程:线程函数 比如:fun....

linux多线程_第1张图片
进程指:一个正在运行的程序


在linux平台创建线程,是内核提供的

头文件#include

注意编译的时候要加 -lpthread

pthread_create(线程id,默认的属性可以不设置为NULLL,参数函数);  创建线程

linux多线程_第2张图片

子线程如下图

linux多线程_第3张图片

 有些时候主线程会不等子线程运行完毕就退出进程,导致子线程来不及打印,可以加个睡眠,但这样效率太低了!!

pthread_join(id,二级指针); 等子线程会在执行后等待子线程结束,子线程会返回给它一个值

linux多线程_第4张图片
pthread_exit();   退出子线程  参数一个字符串,子线程结束后会返回给主线程

linux多线程_第5张图片

 主线程、子线程并发运行

 linux多线程_第6张图片

 运行结果我们可以看出,主线程和子线程是并发运行的,所以打印出的数字不是按顺序打印的,

在极短的时间内,五个线程同时被创建

linux多线程_第7张图片

当我们处理器为双核或者更多时,打印5000会出现4999的情况,这是因为在某一个时间里,两个处理器同时处理了同一个值加一,这种情况在处理器越多的情况下越容易发生数值重合加一

这种情况叫做并发,并发是一种特殊的并行

并发:同一时刻可能只有一个线程在执行,

并行:同一时刻可能有多个线程在执行,一个处理器不可能出现并行,至少两个

线程同步

四种方法:1.互斥锁   2.使用信号量   3.条件变量  4.读写锁

一、信号量解决

我们设置五个线程,让他们累加到5000,通过设置信号量,让创建的线程执行P\V操作后,可以实现线程同步,即使多个处理器也不会出现小于5000的数

头文件#include

sem_t sem;//定义一个信号量

linux多线程_第8张图片

sem_init(传入定义的信号量,是否在进程间共享,初始值)

linux多线程_第9张图片

sem_wait(信号量地址)  //线程的p操作

sem_post(信号量地址)  //线程的v操作

linux多线程_第10张图片

 sem_destroy(信号量地址); //销毁信号量

二、互斥锁

一个线程再用资源时先加个锁,当用完时再解锁,别的线程再使用

pthread_mutex_t mutex;   //创建锁

pthread_mutex_init(定义锁变量,初始化,锁属性一般为NULL); //初始化

pthread_mutex_lock(锁地址) //加锁

pthread_mutex_unlock(锁地址)   //解锁

pthread_mutex_dsetroy(锁地址)   //销毁锁

 三、读写锁

一般用于读较多的情况下

pthread_rwlock_t 变量名  //创建锁变量

pthread_rwlock_init(锁变量地址,属性一般给NULL)  //锁初始化

pthread_rwlock_rdlock()   //读锁

linux多线程_第11张图片

pthread_rwlock_wrlock()  //写锁

linux多线程_第12张图片

pthread_rwlock_unlock()  //解锁

pthread_rwlock_destroy()  //销毁锁

 所谓的临界区一定要赶紧使用完后释放,让别人也继续使用,所以不能有过多的等待

四、条件变量  (提供一种线程间通知的机制)

互斥锁是用于同步线程对共享数据的访问,条件变量是用于在线程之间同步共享数据的值

pthread_cond_t cond; //创建条件变量,同时我们也配合着锁使用,锁是为了保证条件变量可以正常进行

pthread_cond_init(变量,属性一般给NULL)   //初始化

pthread_cond_destroy(变量)   //销毁

pthread_cond_broadcast(变量)    //唤醒所有在条件变量上等待的线程

linux多线程_第13张图片

pthread_cond_signal(变量)      //唤醒某一个在条件变量上等待的线程

linux多线程_第14张图片

pthread_cond_wait(条件变量,互斥锁)     //   为了不让两个线程同时进,所以要加锁

wait做了两步操作:1.把线程放到等待队列上等着被唤醒  2.对互斥锁解锁

当pthread_cond_wait()成功将线程放队列上时解锁,再将锁锁上,因为不想再进或出时被唤醒

linux多线程_第15张图片

strtok不能再线程中使用!!!!!两个线程都调用的话strtok里面的静态指针或者全局指针变量就会失效,定位会出错!!!

必须使用线程安全的版本 strtok_r(数组地址,分隔符,指针地址)

线程安全:多线程程序无论 调度顺序先后,都可以保证得到一个正确的一致的结果,我们就称为线程安全,正确性保证就是安全

如何保证线程安全:1.线程同步  2.使用线程安全函数(可重入函数,比如:函数名_r)

多线程程序如果使用fork 怎么理解?

当线程使用fork时只会产生一条子进程,不管父进程有多少条执行路径,子进程都只启用fork所在的那条路径,其他路径都停止,资源都可以使用的

linux多线程_第16张图片

linux多线程_第17张图片

 结果能看出,fork后子进程只产生了一条main进程,在哪一条路径里fork,就只在哪一条路径上产生一个子进程

如果fork在产生子进程时,其他线程没有加锁,则子进程也没加锁,反之其他线程正在用加锁了,则子进程也有锁

fork只能在没人用锁的使用在复制,当我们在子线程用锁的时候,如下图,去fork就会发现,fork复制的是和子线程一样的锁,但是此时锁已经被子线程给锁住了,所以在fork完的路径里,子进程想要执行pthread_mutex_lock时就会发现被阻塞住了,因为锁已经被子线程锁上了,子进程复制的是被锁上的锁,不能再上一次锁

linux多线程_第18张图片

linux多线程_第19张图片

多线程程序如何使用fork?

 为了解决上面的问题,系统提供了一个方法 pthread_atfork();参数为三个函数指针,分别指向三个函数,分别为:void *prepare(void);它将在fork调用之前被执行,在父进程中执行的用来锁住所有父进程中的互斥锁。 void *parent(void);它将在fork调用创建出子进程之后,在返回之前,在父进程中被执行,作用是释放所有被prepare锁住的互斥锁。 void *child(void); 是在fork返回之前,在子进程中被执行,和parent一样,child作用也是用于释放所有被prepare锁住的互斥锁,该函数成功返回时0,返回失败是错误码。

linux多线程_第20张图片

linux多线程_第21张图片

linux多线程_第22张图片

 线程属性

 setdetachstate相当于pthread_join();

使用时把pthread_create();中第二个参数改为&attr

linux多线程_第23张图片

线程的实现:

1.纯粹用户级线程,由用户管理创建调度,内核不参与感知不到,优点开销小,缺点无法使用多个处理器,只能实现并发!

2.纯粹内核级内核创建并有内核来管理,优点可以使用多个处理器 ,缺点开销大,但可以实现真正意义上的并行!

3.组合级,用户和内核组合对线程的管理

多线程调试

info threads  //查看线程信息,当被调试的主线程,线程id为*1

thread +线程id号  // 切换线程

面试问题

1.线程和进程的区别

答:线程指进程雷部的一条执行路径(序列)  进程:一个正在运行的程序

2.线程的实现

答:纯粹用户级线程,由用户管理创建调度,内核不参与感知不到,优点开销小,缺点无法使用多个处理器,只能实现并发!

纯粹内核级内核创建并有内核来管理,优点可以使用多个处理器 ,缺点开销大,但可以实现真正意义上的并行!

组合级,用户和内核组合对线程的管理

3.线程并发运行

答:举例:当我们处理器为双核或者更多时,打印5000会出现4999的情况,这是因为在某一个时间里,两个处理器同时处理了同一个值加一,这种情况在处理器越多的情况下越容易发生数值重合加一

4.线程通讯的同步方法

答:设置信号量、互斥锁、条件变量、读写锁

5.线程安全

答:多线程程序无论 调度顺序先后,都可以保证得到一个正确的一致的结果,我们就称为线程安全,正确性保证就是安全,使用线程同步和使用线程安全函数(可重入函数,比如:函数名_r)来实现线程安全

你可能感兴趣的:(linux)