Linux C编程学习笔记(7):线程控制

在写线程总结前,鸭鸭先来说说从上一章我们了解到的,进程和线程的异同点~

  • 相同点:
      无论是进程还是线程,都是用来实现多任务并发的技术手段.二者都可以独立调度.在多任务程序中,子进程(子线程)的调度一般与父进程(父线程)平等竞争。
  • 不同点:
    1. 进程是操作系统资源分配的基本单位,线程是调度的基本单位.进程也可以被调度,但是线程是更小的可以调度的单位
    2. 进程在执行过程中拥有独立的内存单元,线程基本上不拥有系统资源,它与同属一个进程的其他线程共享进程拥有的资源
    3. 进程的个体间是完全独立的,而线程间是彼此依存的.多进程环境中,任何一个进程的终止,不会影响到其他进程;而多线程环境中,父线程终止,全部子线程被迫终止(没有了资源)

那么这章,在进程的基础上学习线程~

先来看看线程和进程的关系:

线程是计算机中独立运行的最小单位,运行时占用很少的系统资源.由于每个线程占用的CPU时间是由系统分配的,因此可以把线程看成操作系统分配CPU时间的基本单位.
在用户看来,多个线程是同时执行的
从操作系统调度上看,各个线程是交替执行的
系统不停地在各个线程之间切换,每个线程只有在系统分配给它的时间段内才能取得CPU的控制权,执行线程中的代码
PS.只是针对单CPU单核,在多CPU多核的主机上,多个线程是可以同时运行的
在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义
那么多线程相对于多进程的优点如下:

  1. 在多进程情况下,每个进程都有自己独立的地址空间,而在多线程情况下,同一进程内的线程共享进程的地址空间.因此创建一个新的进程就要花费时间来为其分配系统资源,而创建一个新的线程花费的时间则要少的多
  2. 在系统调度方面,由于进程地址空间独立而线程共享地址空间,线程间的切换速度要远远快过进程间的切换速度
  3. 在通信机制方面,进程间的数据空间相互独立,彼此通信要以专门的通信方式进行,通信时必须经过操作系统.而同一进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,而不必经过操作系统.因此,线程间的通信更加方便和省时
    PS.这三条概括起来就是节约资源和时间
  4. 可以提高应用程序的响应速度
  5. 可以提高多处理器效率
  6. 可以改善程序的结构

虽然线程在进程内部共享地址空间,打开的文件描述符等资源,但是它也有其私有的数据信息:

  • 线程号(thread ID):唯一
  • 寄存器(包括程序计数器和堆栈指针)
  • 堆栈
  • 信号掩码
  • 优先级
  • 线程私有的存储空间

如何创建线程

单线程的程序是按照一定的顺序执行的,如果在主线程里创建线程,程序此时产生分支,变成两个程序执行;其实这个多进程不一样,子进程是通过拷贝父进程的地址空间来实现的,而各线程共享程序代码,一段代码可以被多个线程执行

#include<pthread.h>
int pthread_creat(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

emm..鸭鸭看到这些参数,有点懵= =我们一个一个来看

  • thread:指针,线程创建成功返回创建的线程ID
  • attr:指向pthread_attr_t结构体的指针,指定线程的属性,NULL表示使用默认属性
  • start_routine:函数指针,指向线程创建后要调用的函数,这个被线程调用的函数也称为线程函数
  • arg:指针,指向传递给线程函数的参数

线程创建成功函数返回0,若不为0说明创建线程失败.

错误代码:
EAGAIN:线程数目过多,系统限制创建新的线程
EINVAL:线程属性值非法

线程创建成功后,新创建的线程开始运行第三个参数所指向的函数,原来的线程继续运行

创建线程的其他系统函数:
pthread_t pthread_self(void) //获取本线程的线程ID
int pthread_equal(pthread_t thread1,pthread_t thread2)  //判断两个线程ID是否指向同一线程
int pthread_once(pthread_once_t *once_control,void (*init_routine)(void))  //用来保证init_routine线程函数在进程中只执行一次

现在我们再来详细看看pthread_attr_t结构体~~

typedef struct
{
    int                  detachstate;
    int                  schedpolicy;
    struct sched_param   schedparam;
    int                  inheritsched;
    int                  scope;
    size_t               guardsize;
    int                  stackaddr_set;
    void*                stackaddr;
    size_t               stacksize;
}pthread_attr_t;

这个鸭鸭并没有深究...


线程终止

  1. return 从线程函数返回
  2. 调用pthread_exit()使线程退出
#include<pthread.h>
void pthread_exit(void *retval);

在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程将终止,进程中的所有线程也终止
如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才终止

  • 线程终止有一个很重要的问题,资源释放,特别是临界资源

什么是临界资源?

在一段时间内只能被一个线程所持有

当线程要使用临界资源时需提出请求,如果该资源未被使用则申请成功,否则等待.临界资源使用完以后要释放以便其他线程可以使用

pthread_cleanup_push(),pthread_cleanup_pop()用于自动释放资源.

从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指向的清理函数(如pthread_exit())

这两个函数是以宏形式提供的:
#include<pthread.h>
#define pthread_cleanup_push(routine,arg)
{
    struct _pthread_cleanup_buffer buffer;
    _pthread_cleanup_push(&buffer,(routine),(srg));
    #define pthread_cleanup_pop _pthread_cleanup_pop(&buffer,(exeute));
}          
  • 程序终止还有一个要注意的问题是线程间的同步问题

线程中各个线程的运行是相互独立的,线程终止并不会相互通知,也不会影响其他线程,终止的线程所占用的资源不会随着线程的终止而归还给系统,而是仍为线程所在的进程持有.

线程也有类似进程wait()系统调用来等待其他进程结束
#include<pthread.h>
void pthread_exit(void* retvai);
int pthread_join(pthread_t th,void* thread_return);
int pthread_detach(pthread_t th);

什么是私有数据(TSD)???

一键多值,通过键值访问不同的数据

#include<pthread.h>
int pthread_key_create(pthread_ket_t  *key,void(*destr_function)(void *)); //创建一个键
int pthread_setspecific(pthread_key_t key,const void * pointer));  //为一个键设置线程私有数据
void* pthread_getspecific(pthread_key_t key);  //从一个键读取线程私有数据
int pthread_key_delete(pthread_key_t key);  //删除一个键
  • pthread_key_create:他的第一个参数key为指向键值的指针,第二个参数为一个函数指针,如果指针不为空,则在线程退出时将以key所关联的数据为参数调用destr_function(),释放分配的缓冲区.
    TSD池中分配一项,将其值赋给key供以后访问使用.key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值,即提供了一个同名而不同值的全局变量,一键多值
一键多值靠的是一个关键数据结构数组,即TSD池
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX]={(0,NULL)};
  • pthread_setspecific:该函数将pointer的值(不是内容)与key相关联.用pthread_setspecific为一个键指定新的线程数据时,线程必须先释放原有的线程数据用以回收空间

  • pthread_getspecific:通过该函数得到与key相关联的数据

  • pthread_key_delete:该函数用来删除一个键,删除后,键所占用的内存被释放,但是与该键相关联的线程数据所占用的内存不会被释放.因此,线程数据的释放必须在释放键之前完成


怎样处理线程同步问题?

互斥锁(通过锁机制来实现线程间的同步)

在同一时刻只允许一个线程执行一个关键部分的代码

函数(pthread.h)功能
pthread_mutex_init初始化一个互斥锁
pthread_mutex_destroy注销一个互斥锁
pthread_mutex_lock加锁,如果不成功,阻塞等待
pthread_mutex_unlock解锁
pthread_mutex_trylock测试加锁,如果不成功立刻返回,错误码为EBUSY

1.使用互斥锁前必须先进行初始化

  • 静态赋值法,将宏结构常量PTHREAD_MUTEX_INITIALIZER赋给互斥锁
pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER
  • 通过pthread_mutex_init函数初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);

2.初始化以后就可以给互斥锁加锁了

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); 

加锁时,不论哪种类型的锁,都不可能被两个不同的线程同时得到,其中一个必须等待解锁,在同一进程中的线程,如果加锁后没有解锁,则其他线程将无法再获得该锁

3.接下来就是解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

使用该函数必须满足两个条件,一是互斥锁必须处于加锁状态,二是调用本函数的线程必须是给互斥锁加锁的线程;解锁后如果有其他线程在等待互斥锁,等待队列中的第一个线程获得互斥锁

4.清除互斥锁

int pthread_mutex_destory(pthread_mutex_t *mutex);

清除锁要求处于开放状态,若锁处于锁定状态,函数返回EBUSY;成功执行返回0

条件变量(利用线程间共享的全局变量进行同步的一种机制)

两个动作:
一个等待使用资源的线程等待"条件变量被设置为真"
另一个线程在使用完资源后"设置条件为真"

函数(pthread.h)功能
pthread_cond_init初始化条件变量
pthread_cond_wait基于条件变量阻塞,无条件等待
pthread_cond_timedwait阻塞直到指定时间发生,计时等待
pthread_cond_signal解除特定线程的阻塞,存在多个等待线程时按入队顺序激活其中一个
pthread_cond_broadcast解除所有线程的阻塞
pthread_cond_destory消除条件变量

1.条件变量的初始化

  • 静态赋值法,将宏结构常量PTHREAD_COND_INITIALIZER赋给互斥锁
pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER
  • 通过pthread_cond_init函数初始化
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
cond_attr通常是NULL

2.等待条件成立

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime); 

pthread_cond_wait函数释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒
条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程基于条件变量阻塞
当一个线程改变条件变量的值时,条件变量获得一个信号,使得等待条件变量的线程退出阻塞状态
pthread_cond_timewait不同的地方在于将阻塞直到条件变量获得信号或者经过由abstime指定的时间,也就是说,在给定时刻前没有满足,则返回ETIMEOUT,结束等待

3.激活等待条件成立的线程

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

4.清除条件变量

int pthread_cond_destory(pthread_cond_t *cond);

只有在没有线程等待该条件变量的时候才能清除这个条件变量,否则返回EBUSY

异步信号(利用线程间共享的全局变量进行同步的一种机制)

Linux操作系统中,进程在内核中实现,线程在内核外实现
Linux线程本质上是轻量级的进程
线程同进程一样也可以接受和处理信号,信号也是一种线程间同步的手段
信号与任何线程都是异步的,也就是说信号到达线程的时间是不定的
如果有多个线程可以接收异步信号,则只有一个被选中

int pthread_kill(pthread_t threadid,int signo);
用来向特定的线程发送信号signo
int pthread_sigmask(int how,const sigset_t *newmask,sigset_t *oldmask);
用来设置线程的信号屏蔽码,但对不允许屏蔽的Cancel信号和不允许响应的Restart信号进行了保护
int sigwait(const sigset_t *set,int *sig);
用来阻塞线程,等待set中指定的信号之一到达,并将达到的信号存入*sig中

出错处理

记住两个函数,灵活运用,可以快速发现问题所在

根据错误码获取一个描述错误信息的字符串:
#include<string.h>
char* strerror(int errnum);
根据errno打印对应的错误提示信息:
#include<stdio.h>
void perror(const char *message);

错误码详见另一篇博客~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值