线程的概念与实现方式
线程的概念
进程是正在执行的程序。线程是进程内部的一条执行路径,一个进程可以有多个线程。
进程线程的区别
- 进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 进程有自己的独立地址空间,线程共享进程中的地址空间。
- 进程的创建消耗资源大,线程的创建相对较小。
- 进程的切换开销大,线程的切换开销相对较小。
线程使用
线程相关的接口函数
int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (start_routine)(void),void arg);
用于创建线程,thread:接收线程ID。attr:线程属性。start_rountine:指定线程函数。arg:线程函数传递的参数。
int pthread_exit(void *retval);
退出线程,retval:指定退出程序。
int pthread_join(pthread_t thread, void **retval);
等待thread指定的线程退出,线程未退出时,该方法阻塞。retval:接收thread线程退出时,指定的退出信息。
多线程代码
实例1:
运行结果:
线程并发
运行结果:
以上程序我们有五个线程,每个线程打印三次,将主程序中的i的地址传递给我们的线程,线程获取i的值将其打印。我们将三次打印的值分别分开如下图:
我们发现三次打印的值都一样,只是顺序不一样,是因为线程运行速度不一样所以输出的顺序才会发生改变。此处要注意:例如线程5,我们将i的地址赋值给线程5时,此时i为4,但是当线程获取i的值时,i已经进入了我们第二次获取退出信息的循环,此时i=0,所以线程5才会输出0。
示例二:
求最后一次输出结果。
我们肯定会想到有五个进程,每个进程对count进行后置++,所以最后一次输出肯定是5000,输出结果呢大部分情况下也是5000,但是会有一部分情况下会小于5000,是为什么呢?
多个线程抢占一个cpu,就会进行并发处理,但是如果有多个处理器,就会发生一个处理器处理一个线程,导致并行处理,这样会使得两个线程同时加一,就会少一次自增。
线程的实现方式
线程的实现方式从操作系统的角度分为:用户级,内核级,组合
用户级:开销小,可以创建很多,但不能利用多处理器资源。
内核级:开销大,由内核直接管理,可以利用多处理器资源。
linux操作系统实现方式:内核级,linux比较特殊,没有特定的线程,以进程的方式实现,每个线程都有相对应的struct task_struct;PCB。
线程的同步
线程的同步指的是一个线程在对某个资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才可以操作,也就是协同步调,让线程按预定的先后顺序进行运行。线程同步的方法有四种:信号量,互斥锁,条件变量,读写锁
信号量
信号量特殊的资源总量,p操作获取资源-1,v操作释放资源+1。临界资源:同一时刻只允许一个进程访问的资源。临界区:访问临界资源的代码段。
头文件:#include<semaphore.h>
int sem_init(sem_t* sem,int psshared,unsigned int value);//初始化函数
int sem_wait(sem_t* sem);//p操作
int sem_post(sem_t* sem);//v操作
int sem_destroy(sem_t *sem);//销毁信号量
代码演示:
创建三个线程,让其有序输出ABCABC……
代码:
运行结果:
互斥锁
互斥锁中有加锁和解锁操作。
pthread_mutex_init();//初始化
pthread_mutex_destroy();//销毁
pthread_mutex_lock();//加锁
pthread_mutex_unlock();//解锁
代码举例:
我们上面有一个例题,五个线程,每个线程对一个全局变量进行+1操作执行1000次,我们会发现当内核cpu大于等于2时会出现总数小于5000的情况,因为同时对同一个值进行+1操作,就等于自增一次。我们可以通过互斥锁使得这种情况进行阻塞。
运行结果:每一次最终相加结果为5000.
读写锁
读写锁就和名字一样存在读和写两种操作,不同之处在于可以同时读,但是写的时候不能读,也不能写,也就是说我们进行读操作时可以进行另外的读操作,但不能进行写操作。
pthread_rwlock_rdlock//读操作
pthread_rwlock_wrlock//写操作
pthread_rwlock_unlock//解锁
pthread_rwlock_init//初始化
pthread_rwlock_destroy//删除锁
代码举例:
创建三个线程,两个进行写操作,一个进行读操作。
执行结果:
通过运行结果我们可以发现两次读操作可以同时运行,但是写操作只能独自运行。
条件变量
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
int pthread_cond_init//初始化
int pthread_cond_wait//等待
int pthread_cond_signal//唤醒单个线程
int pthread_cond_broadcast//唤醒所有等待的线程
int pthread_cond_destroy//销毁消息变量
代码演示:
运行结果:
线程的安全
线程安全就是在多线程运行的过程中,不管线程的调度顺序怎么样,最终结果都是安全的。
保证线程安全两点要素:
- 对线程同步,保证同一时刻只有一个线程访问临界资源。
- 在多线程中使用线程安全的函数。(线程安全函数:一个函数能被多个线程同时调用且巴布达省静态条件)
线程同步我们上面已经详细讲述,我们可以通过举例了解一下线程安全函数。
观察上面的代码,我们期待的运行结果应改时输出a1b2c3d4e…但是我们运行之后会发现结果不尽人意:
因为strtok函数中存在一个静态变量,导致运行两个strtok函数出现冲突。
修改后代码如下:
运行结果如下:
线程与fork
fork函数在哪一个线程上仅分裂当前线程。
将线程安全代码线程中加入fork函数,
运行结果如下:
我们会发现仅对当前线程进行了分裂。