线程的概念:
线程是进程内部的一条执行序列(执行流),一个进程可以包含多条线程(main 函数所代表的执行序列,主线程)。通过函数库创建线程—函数线程。一个进程中所有线程是并发执行的。同一个进程的所有线程都有独立的栈区
主线程代表进程执行的第一条线程而已,当主线程通过线程库创建出函数线程以后,两个线程就没有任何区别
进程与线程的区别:
- 线程是执行的最小单位,是CPU调度的最小单位,进程是资源分配的最小单位
- 一个进程可以包含多条线程。进程是独立的执行个体,线程是进程内部的执行序列
- 进程切换效率低,代价大。线程切换效率高,代价小
- 线程中的局部变量不共享,全局变量,堆区变量,文件描述符共享
进程文件描述符共享,全局变量,堆区变量,局部变量不共享 - 进程是安全的,线程是不安全的
- 线程是轻量级的进程。线程通过改变 eax、ebx 等寄存器的值进行切换
线程的实现方式:
1、用户级线程
1)内核简单,但是用户程序实现复杂,线程的创建、调度、管理都需要用户程序自己完成
2)如果进程中的一条线程阻塞,则这个进程被阻塞
3)切换效率高,不需要陷入内核
2、内核级线程
线程的创建、调度、管理都由操作系统内核支持。
效率低,切换时与用户级线程比较,切换效率低,每次切换都要从用户态切换到内核态
3、混合级线程
多个线程用户共享一个内核线程
线程的使用
libpthread.so
线程的创建函数:
int pthread_creat (pthread_t *id, pthread_attr_t *attr, (void*)(*pthread_fun(void*)), void*arg);
id:用于获取系统创建的线程的 ID 值,传递一个变量的地址
attr:线程的属性,默认属性 NULL
pthread_fun 线程函数,指定新建线程的执行序列
arg:传递给线程函数的参数
pthread_creat (); //创建线程是给线程函数传参的方式:
1、值传递 值最大4个字节。
将传递的值直接强转给 void*
arg:类型是void* 记录的传递的值
2、地址传递 实现了线程间的栈区数据共享
将要传递的值的地址转化为 void*
arg:类型是void* 记录的传递的地址
主线程后期对值的修改可能影响函数线程中获取值,函数线程中通过地址对变量修改,也会主线程中变量的值
主线程结束时,默认调用 exit 函数,结束的是进程
测试结论:
1、pthread_creat 创建一个新的线程,新线程执行的指令序列是 pthread_fun 指针指向的函数
2、进程中的线程是并发执行的,线程的执行顺序是不一定的,由系统决定
3、线程是轻量级进程
操作系统为进程提供一个 4G 的虚拟空间,进程中所有的线程都共享虚拟空间,页表也是共享的。
线程的运行在栈上开辟,在主函数运行开辟的栈的上面。这两份空间是同时执行的
创建线程只会在进程中申请栈区资源,用于线程函数的执行,进程中的数据区域、堆区多线程都是使用同一份,都是共享的
一个进程中的所有线程
全局变量 .data 共享
局部变量 .stack 不共享
堆区变量 .heap 共享
文件描述符 fd 共享
线程结束的函数:int pthread_exit (void *reval);
reval:设置线程的退出状态
等待线程结束函数/获取线程退出状态:
int pthread_join (pthread_t id, void **rval);会阻塞运行
waitpid (pid_t pid, … );
主动结束一个线程:int pthread_cancel (pthread_t id);
线程间通讯
1、将数据定义到全局
2、将数据保存到堆区
3、创建线程时,将栈区数据的地址传递给函数线程
线程同步
线程间协同工作,用于多线程访问临界资源时,必须按照一定的先后顺序访问执行
详情
信号量:类似于计数器,记录临界资源的数量
#include <semaphore.h>
sem_t sem;
int sem_init(sem_t *sem, int shared, int val);
shared:信号量是否可以在进程间共享,Linux不支持
val:设置的信号 量的初始值
int sem_wait(sem_t *sem); // P操作wait等待
int sem_post(sem_t *sem); // V操作
int sem_destroy(sem_t *sem); //销毁信号堂
互斥锁(读写锁、互斥锁):一个锁有两种状态 加锁 解锁
#include <pthread.h>
pthread mutex_t mutex //全局变量
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_lock(ptread_mutex_t *mutex); //加锁.会阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试加锁,不会阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex); //销毁锁
线程安全
多线程环境下,操作共享的资源时,可能会发生一些问题 —》 执行的结果不确定
安全原因:
- 线程是并发执行的或者是并行的
- 操作是非原子操作,在并发系统上
- 操作是非互斥的,在并行系统上
- 操作对象是同一个 线程之间数据共享
可重入函数 strtok_r
char *strtok (char *buff, char *flag);
char *strtok_r (char *buff, char *flag, char **rtptr);
POSIX 不能保证线程安全的函数
多线程环境下调用fork创建的子进程
1、如果多线程环境下,一个线程调用fork创建子进程,子进程仅仅会将调用fork的那个线程启动,其他线程并不会启动
2、如果多进程环境下对锁进行加锁,然后创建进程,则子进程中对锁的加锁可能出现死锁–》子进程会复制父进程的锁
3、保证子进程复制父进程的锁是解锁状态
注册函数
int pthread_atfork(void(*parpare)(void),void(*parent)(void),void(*child)(void));
void(*parpare)(void):fork 调用初始,先调用 parpare 函数,其作用是给所有的锁加锁如果有锁处于加锁状态,则 fork 函数会被阻塞,等待解锁。
void(*parent)(void):parent 和 child 函数在 fork 执行完成之后,分别在父进程空间和子进程空间调用,其作用是对所有的锁解锁
void(*child)(void):fork 之前调用
void pthread_fun(void *arg)
{
pthread_mutex_lock(&mutex);
sleep(3):
unlock();
}
int main()
{
.....
sleep(1);保证函数线程能够加锁
pit_t pid=fork();//fork执行时,mutex是函数线程加锁状态
if(pid==0)
{
lock(&mutex);
printf();
}
else
{
lock(&mutex);
printf();
}
}