1. 线程的创建与销毁:
1.1 函数原型:
1.1.1 pthread_create:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
函数作用:
pthread_create函数用于创建除主线程以外的其他线程。
当一个程序由exec启动执行时,称为 初始线程(initial thread) 或 主线程(main thread) 的单个线程就创建了。 其余线程则由 pthread_create 函数创建。
参数:
pthread_t* :用于返回新创建的线程的tid;
pthread_attr_t* :用于传入线程设置属性,包括:优先级、初始栈大小、是否成为一个守护线程 等,通常情况下使用默认配置传入NULL;
void* (*start_routine):由该线程执行的函数;
void *arg: 该线程执行函数的参数。
返回值:
成功返回0, 失败返回错误码(正数):EAGAIN/EINVAL/EPERM
(1)EAGAIN:没有足够的资源创建线程;
(2)EINVAL:attr线程属性参数不正确;
(3)EPERM:no permission,没有权限
注意pthread_create 与套接字函数及大多数系统调用“出错时返回-1并设置errno”的做法并不相同,pthread_create出错时直接返回正数错误码。
1.1.2 pthread_join:
#include <pthread.h>
int pthread_join(pthread_t tid, void **retval);
函数作用:
用于等待一个给定线程的终止,参数 tid用于指定要等待线程的tid,**retval指针用于存储来自所等待线程的返回值(一个指向某个对象的指针)。
返回值:
成功返回0,失败返回错误码:EDEADLOCK/EINVAL/ESRCH
(1)EDEADLOCK:死锁,例如两个线程互相join等待对方
注:pthread_join是一个阻塞函数,要一直等到其所等待的线程结束后才能继续向下执行,因此如果发生两个线程互相join等待彼此时是会发生死锁的
(2)EINVAL:参数tid所指定的线程不是joinable态(tid若处于detach则不能当做被join等待的对象)
或者:另有一个线程正在join等待本线程,及join不能嵌套
(3)ESRCH:参数tid所指定的线程找不到,即这个线程不存在。
1.1.3 pthread_detach:
#include <pthread.h>
int pthread_detach(pthread_t tid);
函数作用:
将线程状态置为“脱离的(detached)”。
返回值:
成功返回0,失败返回错误码:EINVAL/ESRCH
(1)EINVAL:there is a not a joinable thread. 不是joinable的线程,不能调用detach
(2)ESRCH:No thread with the ID could be found. 参数tid指定的线程不存在
1.1.4 pthread_exit:
#include <pthread.h>
void pthread_exit(void *retval);
函数作用:
函数显式的终止的方法是调用pthread_exit。
如果此线程的状态是joinable,则调用pthread_exit时传入的参数 *retval
将传回给 调用了pthread_join正在等待它退出的线程。
注意 *retval 不能指向线程的局部对象,因为当线程终止时局部对象也将被释放。
返回值:
无返回值,通过参数*retval返回一个字符串,
返回到给正在调用pthread_join等待它的线程
(返回到pthread_join第二个参数中)
1.1.5 pthread_cancel:
#include <pthread.h>
int pthread_cancel(pthread_t tid);
函数作用:
通过在一个线程中调用pthread_cancel,可以终止tid指定的另一个线程。
返回值:
成功返回0,失败返回错误码:ESRCH
(1)ESRCH:No thread with the ID could be found.
参数thread指定的线程找不到,不存在
1.1.6 pthread_self:
#include <pthread.h>
pthread_t pthread_self(void);
函数作用:
每个线程都有一个在所属进程内标识自身的ID。线程ID由 pthread_self 返回。
1.2 线程状态:joinable与detached:
一个线程的状态只能有两种:要么是“可汇合的(joinable)”,要么是“脱离的(detached)”。
线程的默认属性是joinable。
joinable: 当一个joinable的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join(往往是main主线程);
detached: 而当一个线程detach脱离后,当它们终止时,所有相关资源都会由系统来自动释放(如线程ID、线程栈空间、线程退出状态等)。
一个调用了detach的线程不能被其他线程join等待。
如果一个线程需要知道另一个线程什么时候终止,那么就必须保持第二个线程的joinable可汇合状态,并在本线程调用pthread_join等待第二个线程退出。
示例:
通过pthread_join在一个线程中获取另一个线程何时退出:
#include <stdio.h>
#include <pthread.h>
void* start_routine(void* arg) {
pthread_exit(NULL);
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, start_routine, NULL);
pthread_join(tid, NULL);
printf("thread is exited.\n");
return 0;
}
1.3 一个线程终止(被终止)的三种方式:
方式一: 线程内调用 pthread_exit
:
如果不带参数:pthread_exit(NULL) 则直接退出;
如果带参数:pthread_exit(*str); 则将字符串str拷贝到 pthread_join(tid, &ptr)的ptr中。
方式二: 线程函数运行完毕后返回:
启动线程的函数(即 pthread_create的第三个参数 void*)(* start_routine))可以返回,返回值是一个 void* 类型的指针,它的返回值就是相应线程的终止状态。
方式三: 调用 pthread_cancel
终止另外一个线程:
线程调用 pthread_cancel 函数给tid线程发送一个cancel终止信号,tid线程收到终止信号后并不会立即终止,而是要看tid线程对cancel信号的处理方式:
忽略 / 立即终止 / 运行至取消点
(取消点:根据POSIX的定义,所有可能会引起线程阻塞的系统调用都是取消点,
例如:pthread_join、pthread_cond_wait、read、write、sem_wait 等)
方式四: 进程退出:
如果进程的main函数返回 或者 任何线程 调用了exit,整个进程就终止,其中包括它的任何线程。
PS: 并不是一定要等待主线程退出时整个进程才会退出,实际上进程内的任何一个线程调用了exit()函数都会使进程退出。
2. 线程互斥锁和条件变量:
#include <pthread.h>
//创建:
int pthread_mutex_init(pthread_mutex_t *restrict_mutex,
const pthread_mutexattr_t *restrict_attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//销毁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
//解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//初始化信号量:
int pthread_cond_init(pthread_cond_t *cond,
pthread_cond_attr_t *cond_attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//销毁信号量:
int pthread_cond_destroy(pthread_cond_t *cond);
//等待信号量:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
//唤醒一个等待队列中的线程:
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒等待队列中的所有线程:
int pthread_cond_broadcast(pthread_cond_t *cond);
关于线程信号量的深入理解:
(1)pthread_cond_wait 实现了一个阻塞队列,每个等待mutex的线程都放在这个等待队列中;
所以 pthread_cond_signal 就是从等待队列中取出一个线程唤醒,pthread_cond_broadcast 则是将等待队列中的所有线程全部唤醒。
(2)pthread_cond_wait 函数内部先对 mutex unlock 解锁,然后再对 mutex lock 加锁。