线程
一个进程可以有多个线程,一个线程必属于一个进程。
进程实现多任务的缺点:
1.进程间切换的成本大
2.进程间通信麻烦且效率低
线程的引入:
1.保留了进程多任务的特性
2.线程间切换和通信效率更高
3.多线程在多核心cpu上可实现真正并发运行。
pthread_create函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
该函数用来创建一个线程。
thread用来接收子线程的id;
attr为线程的属性,默认可设为NULL;
start_routine为子线程程序;
arg为线程的参数,无参数可设为NULL。
pthread_self函数
pthread_t pthread_self(void);
该函数用来获取线程的id。
pthread_join函数
int pthread_join(pthread_t thread, void **retval);
主线程阻塞等待回收线程。
pthread_detach函数
int pthread_detach(pthread_t thread);
主线程分离子线程,分离后不必去回收子线程。
pthread_cancel函数
int pthread_cancel(pthread_t thread);
一个线程可以向另一个线程发出取消命令,让其立即结束。线程取消的配合函数:
- pthread_setcancelstate (子线程设置自己是否允许被取消[被结束]):
PTHREAD_CANCEL_ENABLE//允许被取消 PTHREAD_CANCEL_DISABLE //不许被取消- pthread_setcanceltype (子线程设置自己被取消采取的方式,其前提是允许被取消):
PTHREAD_CANCEL_DEFERRED//等待到可以结束时才结束(如握有资源未释放时。。。)
PTHREAD_CANCEL_ASYNCHRONOUS//立即结束
pthread_exit函数
void pthread_exit(void *retval);
retval用来接收线程结束的返回值,该返回值会最终传给pthread_join函数的参数中。
注意:线程结束可以使用pthread_exit或return结束该进程;使用exit会让整个进程结束。
pthread_cleanup_push函数
void pthread_cleanup_push(void (*routine)(void *),void *arg);
该函数的作用是,当线程非正常结束(被pthread_cancel结束)时,可能握有一些资源未释放(如互斥锁),此时清理压入函数中注册的功能函数routine会被调用,该功能函数可以帮助清理释放。
pthread_cleanup_pop函数
void pthread_cleanup_pop(int execute);
如果线程正常结束了,可以使用该函数取出压入的函数。参数为0时,取出丢弃该注册的函数;参数为1时,取出并且调用该注册的函数。
两函数的使用类似于压栈/出栈操作,先注册的函数后调用。如:
//。。。子线程程序。。。
void thread(void *arg)
{
pthread_cleanup_push(func1, arg);
pthread_cleanup_push(func1, arg);
//.....子线程主体程序
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit();
}
线程同步
应用场景:
子线程阻塞等待被激活,当主线程有工作需要子线程处理时,让其从阻塞状态激活并工作。
线程同步的方法:
信号量、互斥锁、条件变量
信号量
(参见文章《linux应用基础知识点12-IPC之信号量》)
互斥锁
互斥锁(即互斥量)是一种特殊的信号量,其值非0即1,0代表锁定,1代表解锁。互斥锁主要用来实现资源上锁保护。比如有一段代码需要严格保护操作(不能中途被占用甚至修改某资源),则在该段代码前上锁,在该段代码后解锁。
互斥锁初始化:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
mutex为被初始化的互斥锁对象;
attr为锁的属性,默认属性可设为NULL。
锁定互斥锁:
int pthread_mutex_lock(pthread_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);
补充:man 该函数时可能找不到,是因为没有按照线程相关的man手册。解决办法是: sudo apt-get install
manpages-posix-dev.
条件变量
条件变量用来自动阻塞一个线程,直到某条件发生为止。
条件变量初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
cond为要初始化的条件变量;
attr为属性,如果使用默认属性可设为NULL;
成功返回值为0,错误返回错误编号。
等待条件变量:
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
pthread_cond_wait为阻塞等待,pthread_cond_timedwait为超时等待。
通知条件变量:
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal一次只能激活一个阻塞的线程,pthread_cond_broadcast一次则可以激活所有阻塞中的线程。
条件变量销毁:
int pthread_cond_destroy(pthread_cond_t *cond);
应用例程.
条件变量一般会配合互斥锁进行使用。下面是线程2通知线程1条件变量的程序框架:
线程1.
pthread_mutex_lock(&mutex);//互斥锁锁定
while(xxx) //条件如果不成立
pthread_cond_wait(&cond, &mutex); //阻塞等待条件成立
//注意这里用的互斥锁和上面的锁是一个锁
。。。//线程执行
pthread_mutex_unlock(&mutex);//互斥锁解锁
线程2.
pthread_mutex_lock(&mutex);//互斥锁锁定
。。。//修改条件,使线程1的条件满足
pthread_cond_boadcast(&cond); //激活等待的进程,通知条件已经满足
pthread_mutex_unlock(&mutex);//互斥锁解锁
解读:这里有人会疑惑,既然线程1将互斥锁锁上了,线程2怎么还能获得互斥锁呢。这是因为pthread_cond_wait的内部实现机制:
1.当pthread_cond_wait阻塞时,操作系统将该线程放在线程等待队列中,然后解锁互斥锁。
2.等到有pthread_cond_signal或pthread_cond_broadcast通知信号后,线程去竞争锁。
3.竞争到互斥锁则加锁,然后进行之后的操作。