多线程:所有程序并发运行,多条路径可以同时执行
多线程包括:理解线程并发、线程同步方法、线程安全概念
线程指:进程内部得一条执行路径(从主函数的第一行代码到最后一行)
主线程:main 子线程:线程函数 比如:fun....
在linux平台创建线程,是内核提供的
头文件#include
注意编译的时候要加 -lpthread
pthread_create(线程id,默认的属性可以不设置为NULLL,参数函数); 创建线程
子线程如下图
有些时候主线程会不等子线程运行完毕就退出进程,导致子线程来不及打印,可以加个睡眠,但这样效率太低了!!
pthread_join(id,二级指针); 等子线程会在执行后等待子线程结束,子线程会返回给它一个值
pthread_exit(); 退出子线程 参数一个字符串,子线程结束后会返回给主线程
运行结果我们可以看出,主线程和子线程是并发运行的,所以打印出的数字不是按顺序打印的,
在极短的时间内,五个线程同时被创建
当我们处理器为双核或者更多时,打印5000会出现4999的情况,这是因为在某一个时间里,两个处理器同时处理了同一个值加一,这种情况在处理器越多的情况下越容易发生数值重合加一
并发:同一时刻可能只有一个线程在执行,
并行:同一时刻可能有多个线程在执行,一个处理器不可能出现并行,至少两个
四种方法:1.互斥锁 2.使用信号量 3.条件变量 4.读写锁
我们设置五个线程,让他们累加到5000,通过设置信号量,让创建的线程执行P\V操作后,可以实现线程同步,即使多个处理器也不会出现小于5000的数
头文件#include
sem_t sem;//定义一个信号量
sem_init(传入定义的信号量,是否在进程间共享,初始值)
sem_wait(信号量地址) //线程的p操作
sem_post(信号量地址) //线程的v操作
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() //读锁
pthread_rwlock_wrlock() //写锁
pthread_rwlock_unlock() //解锁
pthread_rwlock_destroy() //销毁锁
所谓的临界区一定要赶紧使用完后释放,让别人也继续使用,所以不能有过多的等待
互斥锁是用于同步线程对共享数据的访问,条件变量是用于在线程之间同步共享数据的值
pthread_cond_t cond; //创建条件变量,同时我们也配合着锁使用,锁是为了保证条件变量可以正常进行
pthread_cond_init(变量,属性一般给NULL) //初始化
pthread_cond_destroy(变量) //销毁
pthread_cond_broadcast(变量) //唤醒所有在条件变量上等待的线程
pthread_cond_signal(变量) //唤醒某一个在条件变量上等待的线程
pthread_cond_wait(条件变量,互斥锁) // 为了不让两个线程同时进,所以要加锁
wait做了两步操作:1.把线程放到等待队列上等着被唤醒 2.对互斥锁解锁
当pthread_cond_wait()成功将线程放队列上时解锁,再将锁锁上,因为不想再进或出时被唤醒
如何保证线程安全:1.线程同步 2.使用线程安全函数(可重入函数,比如:函数名_r)
当线程使用fork时只会产生一条子进程,不管父进程有多少条执行路径,子进程都只启用fork所在的那条路径,其他路径都停止,资源都可以使用的
结果能看出,fork后子进程只产生了一条main进程,在哪一条路径里fork,就只在哪一条路径上产生一个子进程
如果fork在产生子进程时,其他线程没有加锁,则子进程也没加锁,反之其他线程正在用加锁了,则子进程也有锁
fork只能在没人用锁的使用在复制,当我们在子线程用锁的时候,如下图,去fork就会发现,fork复制的是和子线程一样的锁,但是此时锁已经被子线程给锁住了,所以在fork完的路径里,子进程想要执行pthread_mutex_lock时就会发现被阻塞住了,因为锁已经被子线程锁上了,子进程复制的是被锁上的锁,不能再上一次锁
为了解决上面的问题,系统提供了一个方法 pthread_atfork();参数为三个函数指针,分别指向三个函数,分别为:void *prepare(void);它将在fork调用之前被执行,在父进程中执行的用来锁住所有父进程中的互斥锁。 void *parent(void);它将在fork调用创建出子进程之后,在返回之前,在父进程中被执行,作用是释放所有被prepare锁住的互斥锁。 void *child(void); 是在fork返回之前,在子进程中被执行,和parent一样,child作用也是用于释放所有被prepare锁住的互斥锁,该函数成功返回时0,返回失败是错误码。
setdetachstate相当于pthread_join();
使用时把pthread_create();中第二个参数改为&attr
info threads //查看线程信息,当被调试的主线程,线程id为*1
thread +线程id号 // 切换线程
1.线程和进程的区别
答:线程指进程雷部的一条执行路径(序列) 进程:一个正在运行的程序
2.线程的实现
答:纯粹用户级线程,由用户管理创建调度,内核不参与感知不到,优点开销小,缺点无法使用多个处理器,只能实现并发!
纯粹内核级内核创建并有内核来管理,优点可以使用多个处理器 ,缺点开销大,但可以实现真正意义上的并行!
组合级,用户和内核组合对线程的管理
3.线程并发运行
答:举例:当我们处理器为双核或者更多时,打印5000会出现4999的情况,这是因为在某一个时间里,两个处理器同时处理了同一个值加一,这种情况在处理器越多的情况下越容易发生数值重合加一
4.线程通讯的同步方法
答:设置信号量、互斥锁、条件变量、读写锁
5.线程安全
答:多线程程序无论 调度顺序先后,都可以保证得到一个正确的一致的结果,我们就称为线程安全,正确性保证就是安全,使用线程同步和使用线程安全函数(可重入函数,比如:函数名_r)来实现线程安全