1.什么是线程
轻量级的进程,可实现多任务的并发。
进程是操作系统资源分配的最小单位;
线程是操作系统任务调度的最小单位。
2. 线程的创建
线程由某个进程创建。
进程创建线程时,会为其分配独立的(8M)栈区空间;
线程和所在进程,以及进程中的其他线程,共用进程的堆区、数据区、文本区。
3. 线程的调度
宏观并行,微观串行
4. 线程消亡
1. 线程退出
2. 回收线程资源空间
5. 进程和线程的区别
进程:进程是操作系统资源分配的最小单位;
资源消耗:进程资源开销大,每次创建都需要有0-4G的虚拟内存空间
效率角度:由操作系统创建,创建时耗时比线程大;跨进程调度比跨线程调度慢;
通信方面: 进程间不能直接通信,需要使用进程间通信机制(IPC机制)
安全性角度:进程安全性比线程高,各进程空间独立线程:线程是操作系统任务调度的最小单位。
资源消耗:资源开销较小,只需要所在进程为其开辟8M的栈区空间
效率角度:由所在进程创建;跨进程调度比跨线程调度慢;
通信方面:通信简单,可以使用线程共享的区域进行通信(比如全局变量)
安全性角度:线程没有进程安全性好,一个线程异常可能影响同一进程中的所有线程
6.线程的相关编程
1. 线程的创建: pthread_create()
pthread_self():获取当前线程的ID号
2. 线程调度:由操作系统调度
3. 线程消亡:
1. 线程退出:pthread_exit(); 在线程任务函数中使用return结束线程
2. 线程回收:pthread_join();
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数:
thread : 保存线程ID的变量地址
attr:线程属性的对象地址
NULL : 按照默认属性创建
start_routine:函数的指针:指向线程启动后要执行的任务(线程任务函数)
arg:为线程任务函数传递的参数
返回值:
成功:0
失败:非0void pthread_exit(void *retval);
功能:退出一个线程任务
参数:retval:向回收线程传递的参数的地址
NULL:表示不传递参数
int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待回收线程资源空间
参数:thread:要回收的线程ID
retval:用来保存线程退出时传递的参数
返回值:
成功:0
失败:-1
7.线程回收策略
线程回收策略:
1.分离属性的线程:(没有空闲的线程可帮忙回收)pthread_detach()
2.非分离属性线程:pthread_join()阻塞回收
1.分离属性:不需要被其他线程回收的线程称为分离属性得到线程,将来会被操作系统所回收
2.非分离属性:可以被其他线程回收,或者结束的线程,称为非分离属性的线程(默认属性:非分离属性)
8.线程间通信
全局变量通信:
临界资源:多个线程可以同时访问的资源称为临界资源。比如(全局变量、共享内存区域等)
资源竞争:多个线程在访问临界资源时,存在资源竞争。
互斥机制:多个线程访问临界资源时,具有排他性访问机制(一次只允许一个线程对该临界资源进行访问)
互斥锁:解决资源竞争问题
实现步骤:
1.创建互斥锁;pthread_mutex_t
2.初始化互斥锁;pthread_mutex_init
3.加锁;pthread_mutex_lock(pthread_mutex_t *mutex)
4.解锁;pthread_mutex_unlock(pthread_mutex_t *mutex)
5.销毁锁;pthread_mutex_destory(pthread_mutex_t *mutex)
init pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t restrict attr);
功能:初始化锁
参数:
mutex:锁对象地址
attr: 锁的属性(NULL:默认属性)
返回值:
成功:0
失败:-1
线程间互斥锁防止资源竞争,以下代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int num = 10000;
pthread_mutex_t mutex;
void* thread_function1(void *arg)
{
pthread_mutex_lock(&mutex);
int i = 0;
for (i = 0; i < 100000; ++i)
{
num += 1;
printf("num = %d\n", num);
}
pthread_mutex_unlock(&mutex);
return NULL;
}
void* thread_function2(void *arg)
{
pthread_mutex_lock(&mutex);
int i = 0;
for (i = 0; i < 100000; ++i)
{
num += 1;
printf("num = %d\n", num);
}
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
int ret1 = pthread_create(&thread1, NULL, thread_function1, NULL);
if (ret1 != 0)
{
printf("pthread_create error\n");
return -1;
}
int ret2 = pthread_create(&thread2, NULL, thread_function2, NULL);
if (ret2 != 0)
{
printf("pthread_create error\n");
return -1;
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
9.线程间的同步机制
设计模式:
开闭原则:对增加开放,对修改关闭
异步:多线程、多进程本质是异步执行
线程间同步机制:让多个线程在执行摸个任务时,具有先后顺序的执行。
信号量:实现线程间同步。
操作步骤:
1.定义信号量对象:sem_t
2.初始化信号量:sem_init();
3.申请信号量:p操作:int sem_wait(sem_t *sem);
释放信号量:v操作:int sem_post(sem_t *sem);
PV操作
4.销毁信号量:int sem_destroy(sem_t *sem)
头文件:#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem: 要初始化的信号量对象地址
pshared:
0:线程间共享
非0:进程间共享
value: 信号量的初始值(初始资源数)
代码实现:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t sem1;
sem_t sem2;
sem_t sem3;
void *fun1(void *arg)
{
while(1)
{
sem_wait(&sem1); //等待信号量1变为0,然后执行后面的语句
puts("A->");
sem_post(&sem2); //信号量0变为1,然后执行后面的语句
sleep(1);
}
}
void *fun2(void *arg)
{
while(1)
{
sem_wait(&sem2); //等待信号量2变为0,然后执行后面的语句
puts("B->");
sem_post(&sem3); //信号量0变为1,然后执行后面的语句
sleep(1);
}
}
void *fun3(void *arg)
{
while(1)
{
sem_wait(&sem3);
puts("C->");
sem_post(&sem1);
sleep(1);
}
}
int main(int argc, char const *argv[])
{
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
pthread_t tid1,tid2,tid3;
pthread_create(&tid1, NULL, fun1, NULL);
pthread_create(&tid2, NULL, fun2, NULL);
pthread_create(&tid3, NULL, fun3, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
return 0;
}
互斥锁、信号量、读写锁、自旋锁 ------>锁
死锁:死锁指的是在多线程环境中,每个执行流(线程)都有未释放的资源,且互相请求对方未释放资源,从未导致陷入永久等待状态的情况。
现象:
现象1:忘记释放锁
现象2:重复加锁
现象3:多线程多锁,抢占资源不当
如:线程A获取了1锁,线程B获取了2锁,同时线程A还想获取2锁,线程B还想获取1锁
产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用(一个执行流获取锁后,其他执行流不能再获取该锁)
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放(执行流本身用着一把锁并不释放,还在请求别的锁)
(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺(A执行流拿着锁,其他执行流不能释放)
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(多个执行流拿着对方想要的锁,并且各执行流还去请求对方的锁)。
解决方法:
1.锁一定要成对出现
2.使线程的加锁顺序一致
3.破坏环路等待条件
使用非阻塞锁,一旦线程发现请求的锁被使用,就去释放自己拥有的锁
pthread_mutex_trylock();
int sem_trywait(sem_t *sem);
IPC机制:
1.管道
有名管道:用于同一主机,任意进程间通信
无名管道:只能用于同一主机,具有亲缘关系的进程间通信(父子进程间)