请选择 进入手机版 | 继续访问电脑版
设为首页收藏本站

嵌入式天空

 找回密码
 我要注册

扫一扫,访问微社区

Unix高级编程之十三 线程控制

2015-2-7 20:35| 发布者: lizhiyong| 查看: 1012| 评论: 0|原作者: 李志勇

摘要: 线程控制 线程限制 可以调用sysconf(_SC_THREAD_THREADS_MAX)来查看系统中每个进程最多能创建的线程数量,如果返回-1并且没有设置错误码则说明进程对这个没有限制 线程属性 初始化线程属性 int pthread_attr_init(p ...

线程控制

线程限制

可以调用sysconf(_SC_THREAD_THREADS_MAX)来查看系统中每个进程最多能创建的线程数量,如果返回-1并且没有设置错误码则说明进程对这个没有限制

 

线程属性

初始化线程属性

int pthread_attr_init(pthread_attr_t *attr);

 

反初始化线程属性

int pthread_attr_destroy(pthread_attr_t *attr);

 

设置线程分离属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

      

获取线程分离属性

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

 

互斥量属性

初始化互斥量属性

int pthread_mutexattr_init(pthread_mutexattr_t *attr);

 

反初始化互斥量属性

int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

 

获得互斥量的类型属性

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);

 

设置互斥量的类型属性

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

 

线程安全

有很多函数是线程不安全的(参考apue

 

对于FILE*文件句柄Posix提供了多线程的安全使用方法,在使用FILE*之前调用以下函数获得锁

void flockfile(FILE *filehandle);

int ftrylockfile(FILE *filehandle);

 

在使用FILE*完毕后调用以下函数释放锁

void funlockfile(FILE *filehandle);

 

注意:这几个函数处理的单位是FILE*句柄,而不是文件,所以对于同一个文件的多个FILE*分别调用flockfile是都可以获得锁的

 

以下几个函数是不加锁的,目的是为了防止锁的过度使用而降低性能

int getc_unlocked(FILE *stream);

int getchar_unlocked(void);

int putc_unlocked(int c, FILE *stream);

int putchar_unlocked(int c);

 

注意:这几个函数必须在flockfile的保护下才能使用

 

线程私有数据

有些函数可能会在内部分配一些内存,然后在内存中写入一些需要返回给调用者的数据,并且把指针返回给调用者(比如ctime函数),那么这样的函数是非线程安全的,解决这个问题的方法有两个,一是改为调用者分配空间,二是使用线程私有数据

 

有时候线程会被取消而来不及释放已经分配的内存,这个问题也可以用线程私有数据解决

 

创建一个键

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));  

key是键,key可以由与创建线程处于同一个进程的其他线程调用,key由调用者定义(或分配),destructor是回调函数,当线程正常退出时候该回调函数会被调用,参数便是指向线程私有数据的指针

 

注意:多个线程可以安全的使用同一个keypthread_key_create对于同一个键key只能调用一次                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         

 

删除一个键(解除键与线程特定数据的关联)

int pthread_key_delete(pthread_key_t key);

 

设置与键关联的线程私有数据(调用线程的私有数据)

int pthread_setspecific(pthread_key_t key, const void *value);

 

获得与键关联的线程私有数据(调用线程的私有数据)

void *pthread_getspecific(pthread_key_t key);

 

保证多线程中某一个函数不会被重复调用

pthread_once_t once_control = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

      

注意:once_control必须是一个全局变量(所有线程均可见),多个线程调用了pthread_once,系统会保证init_routine函数只会运行一次

 

线程的取消

调用pthread_cancel函数只是给线程发送一个取消请求,只有当线程执行到取消点之后线程才有可能退出(如果线程设置为不可取消,则会忽略取消请求)

 

带取消点的函数参考apue

 

在线程中添加取消点

void pthread_testcancel(void);

在执行这个函数的时候检查是否有取消信号,如果有并且线程的取消状态是PTHREAD_CANCEL_ENABLE,则线程被取消

 

设置线程是否可取消   

int pthread_setcancelstate(int state, int *oldstate);

如果statePTHREAD_CANCEL_ENABLE,则该线程可被取消(线程的默认属性)

如果statePTHREAD_CANCEL_DISABLE,则线程取消信号会被阻塞,直到线程的的取消属性变为PTHREAD_CANCEL_ENABLE

 

设置线程的取消类型

int pthread_setcanceltype(int type, int *oldtype);

如果typePTHREAD_CANCEL_DEFERRED,则线程在执行到取消点时候才会被取消(线程的默认属性)

如果typePTHREAD_CANCEL_ASYNCHRONOUS,则线程随时都可以被取消

 

线程和信号

由于信号要比线程模型出现的早,所以在线程中使用信号会有诸多问题

 

在线程中使用信号要注意以下几点:

1.如果发送给某一个线程一个能够终止进程的信号,那么整个进程就会终止

2.多个线程会共享同一信号的处理,比如线程1忽略了信号A,但是线程2又给信号A安装了信号处理函数,这个时候无论信号A发送到哪一个线程,信号处理函数都会被调用

3.发送到进程的信号,内核会选择任意一个线程来处理

4.每个线程都有独立的信号屏蔽字,在线程中应该调用pthread_sigmask来设置或者修改信号屏蔽字,新创建的线程继承创建线程的信号屏蔽字

5.内核为每个线程和进程分别维护了一个未决信号的表。当使用sigpending()时,该函数返回的是整个进程未决信号表和调用该函数的线程的未决信号表的并集。当新线程被创建时,线程的pending signals被设置为空。当线程A阻塞某个信号S后,发送到A中的信号S将会被挂起,直到线程取消了对信号S的阻塞。

6.pthread_xx系列的函数是非异步信号安全的,所以在信号处理函数中不要调用这些函数

 

修改线程的信号屏蔽字

int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

这个函数改变调用线程的信号屏蔽字,改变之后再创建的新线程都继承当前信号屏蔽字

 

等待信号

int sigwait(const sigset_t *restrict set, int *restrict sig);

该函数会把set中的信号解除屏蔽然后阻塞等待set中的信号出现,信号出现后函数返回并且会恢复调用该函数之前的信号屏蔽字,如果在调用该函数前信号就发生了,则这个函数不会阻塞,sig中返回收到的信号

 

注意:在调用sigwait函数之前一定要先阻塞set中的信号

 

发送信号到特定线程

int pthread_kill(pthread_t thread, int sig);

如果是一个会杀死进程的信号,则线程收到信号后进程会终止

 

在多线程程序中处理信号我们一般采用以下方案:

单独启动一个线程调用sigwait等待信号和处理信号,其他线程都阻塞要处理的信号

 

线程和fork

在一个多线程的进程中调用fork时候很容易引起死锁,因为fork会为子进程复制父进程的整个页表,因此父进程的互斥量、读写锁、条件变量(以及他们当时的状态)等都会被复制到子进程,但是只有调用fork的单一线程被复制到子进程(其他执行线程不复制),因此子进程很有可能是由于死锁而一直等待,原因是子进程不太清楚锁的状态,万一在fork之前某一个执行线程获得了锁而fork之后调用的函数中也要获得锁,那么这时候子进程就死锁了(因为在子进程中没有线程释放锁)

fork之后立马调用exec的话就没有问题

 

Fork处理函数

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

调用这个函数可以安排三个处理函数:

Prepare函数会在fork的正式代码开始执行之前被调用

Parent函数会在创建子进程之后fork返回之前由父进程调用

Child函数会在创建子进程之后fork返回之前由子进程调用

 

利用pthread_atfork提供的机制,我们可以在prepare函数中获得所有锁(确保fork出的子进程的锁的状态是确定的),在parent中释放所有锁(只是父进程的),在child中释放所有锁(只是子进程的),这样的话子进程中的锁就都是空闲的状态(没有被加锁),接下来子进程便可以正常使用从父进程中复制过来的锁了

 

注意:但是pthread_atfork并不能解决条件变量的带来的问题,所有pthread_atfork函数还是不完善的,因此在创建多个线程之后尽量不要fork(除非立即调用exec),如果多线程和多进程不得不共存,那么尽量在创建线程之前先创建好所有进程


鲜花

握手

雷人

路过

鸡蛋

最新评论

QQ|Archiver|手机版|小黑屋|EBMSKY Inc. ( 冀ICP备17022971号-1  

GMT+8, 2019-8-22 02:12 , Processed in 0.050143 second(s), 26 queries .

Powered by Discuz! X3.2

© 2014-2018 Comsenz Inc. 【嵌入式天空】设计

返回顶部