09-线程(线程管理、信号量、互斥量)
线程
新线程拥有自己的栈,因此有自己的局部变量,如:线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量、线程私有数据。但与创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。Linux线程与POSIX标准线程存在一定细微区别,最明显的是信号处理部分。差别大都受底层Linux内核限制。创建线程
大多数函数名都以pthread_
开头,使用他们必须定义宏_REENTRANT
,包含头文件pthread.h
,并使用-lpthread
链接线程库。
最初设计库例程时假设只有一个线程执行。errno 就是一个例子,默认只有一个线程访问。在一个线程准备获取刚刚的错误代码时,该变量很容易被另一个线程中函数调用所改变。类似的问题还出现在fputs之类的函数中,它们通常使用一个全局性的区域来缓存输出数据。
为解决问题使用可重入的例程,可重入代码可被不同的线程多次调用而仍正常工作,也可以是某种形式的嵌套调用。因此代码中可重用部分通常只使用局部变量,这使得每次对改代码的调用都获得它自己的唯一一份数据副本。
编写多线程时要在所有的 #include
前定义宏 _REENTRANT
告诉编译器我们需要可重入的功能。它将做三件事:
- 它为部分函数重新定义可安全重入版本,这些函数名后加上
-r
字符串。如:gethostbyname
->gethostbyname_r
stdio.h
中原来以宏的形式实现的一些函数将变为可安全重入的函数- 在
errno.h
中定义的变量errno
将变为一个函数调用,能以一种多线程安全方式获取真正的errno值
使用pthread.h
头文件,链接pthread
函数库。pthread_
系列函数成功时返回0,失败时返回错误代码
#include <pthread.h>
typedef unsigned long int pthread_t;
// 线程相等返回0
int pthread_equal (pthread_t __thread1, pthread_t __thread2);
// 获得自身线程id
pthread_t pthread_self (void);
// 进程调用线程的浮点环境和信号屏蔽字,返回错误码不设置errno
int pthread_create (pthread_t *__restrict __newthread, // 存放线程标识符
const pthread_attr_t *__restrict __attr, // 线程属性,可为NULL
void *(*__start_routine) (void *), // 线程将要启动的函数
void *__restrict __arg); // 启动函数的参数
/*
单个线程3种方式退出:
1. 函数执行结束,返回值为退出码
2. 线程可以被同一进程种其他线程取消
3. 线程调用 pthread_exit
终止调用它的线程,返回指向某个对象的指针。
注:不能返回栈中的局部变量,因线程调用此此函数后就销毁
*/
void pthread_exit (void *__retval); // 参数可以通过 pthread_join 接收
/*
线程正常结束,返回值包含返回码
线程被取消,返回值指定的内存单元设置为 PTHREAD_CANCELED
通过pthread_join 可以将线程置于分离状态,这样资源就可以恢复。如果线程已处于分离状态,pthread_join 调用就会失败,返回 EINVAL。
*/
int pthread_join (pthread_t __th, // 要等待的线程
void **__thread_return); // 指向指针的指针->指针->线程返回值
/*
**请求**取消同一进程中其他线程
目标线程可以选中忽略或控制如何被取消;取消时表现如同调用参数为 PTHREAD_CANCELED 得 pthread_exit 函数
*/
int pthread_cancel (pthread_t __th);
/*
线程清理处理程序,与进程中atexit函数类似
记录在栈中,执行顺序与注册顺序相反
线程执行以下动作时,清理函数rtn由pthread_cleanup_push函数调用:
1. 调用 pthread_exit 时
2. 响应取消请求时
3. 用非零execute参数调用 pthread_cleanup_pop 时
* 如果是从启动例程中正常返回,则不会调用pthread_cleanup_push
如果execute参数为0,清理函数将不被调用。无论那种情况,pthread_cleanup_pop 都将删除上次pthread_cleanup_push 调用建立得清理处理程序。
*/
void pthread_cleanup_push(void(*rtn)(void*), void* arg);
void pthread_cleanup_pop(int execute);
// ubuntu中函数实现如下
# define pthread_cleanup_push(routine, arg) \
do { \
__pthread_cleanup_class __clframe (routine, arg)
# define pthread_cleanup_pop(execute) \
__clframe.__setdoit (execute); \
} while (0)
/*
默认情况下线程得终止状态会保存到对该线程调用 pthread_join,就像父线程要对子线程调用 wait。
如果线程被分离,则线程底层存储资源在线程终止时立即被收回。已经被分离的线程无法使用join操作
*/
int pthread_detach (pthread_t __th);
编译命令:
gcc -D_REENTRANT main.c -lpthread
或 gcc -D_REENTRANT main.c -lpthread -L/usr/lib/nptl
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char message[] = "Hello World!";
void *thread_func(void *arg)
{
printf("thread_func is running\n");
sleep(3);
strncpy(message, "Bye!", strlen(message));
pthread_exit("This is from other thread.");
}
int main(int argc, char const *argv[])
{
int res;
pthread_t a_thread;
void *thread_result;
res = pthread_create(&a_thread, NULL, thread_func, (void *)message);
if (res != 0) {
perror("Thread create failed");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish\n");
res = pthread_join(a_thread, &thread_result);
if (res != 0) {
perror("Thread join failed\n");
exit(EXIT_FAILURE);
}
printf("Thread joined, it returned: [%s]\n", (char *)thread_result);
printf("message is \"%s\"\n", message);
return 0;
}
同步
信号量
两种接口,一组是对POSIX的拓展,一组称为系统V信号量。
信号量是一种特殊变量,可以i被增加或减少,对其关键访问保证原子性。此处介绍二进制变量,只有0与1两种取值;还有一种更通用的信号量计数信号量。信号量函数都以sem_
开头。
#include <semaphore.h>
int sem_init (sem_t *__sem, // 需要初始化的sem变量
int __pshared, // 控制信号量类型,为0表示这个信号量是当前进程局部信号量,否则可在多进程间共享。Linux暂不支持非零值。
unsigned int __value);
int sem_post (sem_t *__sem); // 以原子方式为sem加一
int sem_wait (sem_t *__sem); // 以原子方式减一
int sem_destroy (sem_t *__sem); // 清理sem拥有的所有资源,若企图清理的信号量正被一些线程等待,会收到一个错误
例子,一个线程读取输入,一个线程统计字符数。但此处没有对工作区的互斥访问
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WORK_SIZE 1024
sem_t bin_sem;
char work_area[WORK_SIZE];
void *thread_fun(void *arg)
{
sem_wait(&bin_sem);
while (strncmp("end", work_area, 3) != 0) {
printf("You input %ld characters\n", strlen(work_area) - 1);
sem_wait(&bin_sem);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
int res;
pthread_t a_thread;
void *thread_result;
res = sem_init(&bin_sem, 0, 0);
if (res != 0) {
perror("Semaphor initalization failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&a_thread, NULL, thread_fun, NULL);
if (res != 0) {
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Input some text. Enter 'end' to finish\n");
while (strncmp("end", work_area, 3) != 0) {
fgets(work_area, WORK_SIZE, stdin);
sem_post(&bin_sem);
}
printf("\nWaiting for thread to finish...\n");
res = pthread_join(a_thread, &thread_result);
if (res != 0) {
perror("Thread join failed");
exit(EXIT_FAILURE);
}
sem_destroy(&bin_sem);
exit(EXIT_SUCCESS);
return 0;
}
互斥量
成功返回0,失败返回错误代码,但并不设置errno
#include <pthread.h>
PTHREAD_MUTEX_INITIALIZER // 宏定义的默认属性互斥锁
/*
默认类型fast,这种类型对已加锁的互斥量加锁会导致阻塞,又因为互斥量被这个线程所拥有,所以死锁
可以改变属性,使其检查到就返回一个错误,或允许递归、可重入锁
*/
int pthread_mutex_init (pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr);//attr可为NULL
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
int pthread_mutex_lock (pthread_mutex_t *__mutex);
int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
const struct timespec *__restrict __abstime);
int pthread_mutex_trylock (pthread_mutex_t *__mutex)
int pthread_mutex_unlock (pthread_mutex_t *__mutex);
读写锁
PTHREAD_RWLOCK_INITIALIZER // 宏定义常量,默认属性读写锁
int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,
const pthread_rwlockattr_t *__restrict __attr);
int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock);
// 存在错误返回,检查返回值
int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock);
int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock);
int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock);
// 可以获取时返回0。否则,返回错误 EBUSY
int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);
int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);
int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock,
const struct timespec *__restrict __abstime);
int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock,
const struct timespec *__restrict __abstime);
条件变量
注意:当先signal在wait会导致信号丢失,然后无限阻塞。
// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
//成功则返回0, 出错则返回错误编号.
// 申请资源,P操作
// **注意**:在wait函数阻塞时会释放其绑定的互斥体,所以在wait函数前lock这个mutex
// 在wait函数返回时会对互斥体加锁,所以在wait函数后unlock这个mutex
/*
注意虚假唤醒:
while (list.empty()) {
pthread_cond_wait(&cond_, &mutex_);
}
*/
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
//成功则返回0, 出错则返回错误编号.
// 释放资源,V操作
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
//成功则返回0, 出错则返回错误编号.
屏障
屏障时用户协调多个线程并行工作的同步机制。它允许每个线程等待,直到所有合作线程都到达某一点,然后从该点继续执行。pthread_join 就是一种屏障。
int pthread_barrier_init (pthread_barrier_t *__restrict __barrier,
const pthread_barrierattr_t *__restrict __attr,
unsigned int __count);// 指定继续运行前必须到达屏障的线程数
int pthread_barrier_destroy (pthread_barrier_t *__barrier);//成功返回0,失败返错误编号
/*
建立屏障,等待其他线程
调用此函数的线程在屏障计数未满足条件时进入休眠状态。第count个线程调用此函数时满足了屏障计数,所有线程被唤醒。
对于一个任意线程,函数返回PTHREAD_BARRIER_SERIAL_THREAD,其余的返回0。这个线程可以看为主线程,它可以工作在其他所有线程完成的工作结果上。
*/
int pthread_barrier_wait (pthread_barrier_t *__barrier);
自旋锁
用于锁被持有时间短,而且线程并不希望再重新调度上花费太多成本。一般用于内核,用户层编程不要使用。
extern int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared);
extern int pthread_spin_destroy (pthread_spinlock_t *__lock);
extern int pthread_spin_lock (pthread_spinlock_t *__lock);
extern int pthread_spin_trylock (pthread_spinlock_t *__lock);
extern int pthread_spin_unlock (pthread_spinlock_t *__lock);
线程属性
守护线程又称分离线程(detached thread),可通过pthread_detach
或修改线程属性创建。
#include <pthread.h>
int pthread_attr_init (pthread_attr_t *__attr); // 成功返回0,错误返回错误代码
int pthread_attr_destroy (pthread_attr_t *__attr);
int pthread_attr_getdetachstate (const pthread_attr_t *__attr, int *__detachstate);
int pthread_attr_setdetachstate (pthread_attr_t *__attr, int __detachstate);
int pthread_attr_getschedpolicy (const pthread_attr_t *__restrict __attr, int *__restrict __policy);
int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy);
/*
类似detachstate和schedplicy的还有很多
detachedstate
PTHREAD_CREATE_DETACHED 不能调用 pthread_join
PTHREAD_CREATE_JOINABLE
schedpolicy 控制属性调度方式
SCHED_OTHER
SCHED_RP 循环调度 只许root使用,有实时调度功能
SCHED_FIFO 先进先出 只许root使用,有实时调度功能
schedparam 与前者联合使用,对 SCHED_OTHER 策略运行的线程调度进行控制
inheritsched
PTHREAD_EXPLICIT_SCHED 默认,调度由属性明确设置
PTHREAD_INHERIT_SCHED 新线程将沿用创建者的参数
scope 控制线程调度的计算方式,目前只支持 PTHREAD_SCOPE_SYSTEM
*/
取消线程
取消操作允许线程请求终止其所在进程总的任何其他线程。
- 通过pthread_testcancel 调用已编程方式建立线程取消点
- 线程等待pthread_cond_wait或pthread_cond_timewait中的特定条件
- 被sigwait(2)阻塞的函数
- 一些标准的库调用。通常这些调用包括线程可基于阻塞的函数
POSIX标准中,pthread_join(),pthread_testcancel().pthread_cond_wait(),pthread_cond_timewait(),sem_wait(),sigwait()等函数以及read,write等会引起阻塞的系统调用。
#include <pthread.h>
// 向目标线程发请求取消它
int pthread_cancel (pthread_t __th);
// 设置自己的取消状态,第一个参数可取 PTHREAD_CANCEL_ENABLE 允许线程接收取消请求;或 PTHREAD_CANCEL_DISABLE 忽略取消请求
// 接收取消请求后,用 pthread_setcanceltype 设置取消类型
int pthread_setcancelstate (int __state, int *__oldstate);
/*
type 有两个取值
PTHREAD_CANCEL_ASYNCHRONOUS 立即取消
PTHREAD_CANCEL_DEFERRED 等到下述函数之一再采取行动
pthread_join pthread_cond_timedwait pthread_cond_wait pthread_testcancel sem_wait sigwait
*/
int pthread_setcanceltype (int __type, int *__oldtype);
线程特殊数据
系统支持特定数量的线程特定数据,POSIX要求不小于128个。每个进程维护一个数组,数组元素是此索引位是否被使用的标识,还有线程退出时释放对应空间的函数。
通过 pthread_key_create
获取到当前没有被使用的索引位,然后通过 pthread_getspecific
pthread_setspecific
读写数据。
#include <pthread.h>
int pthread_once(pthread_once_t *once_control, // 指向值为 PTHREAD_ONCE_INIT 变量的指针
void (*init_routine)(void)); // 指向的函数只会在第一次时被调用
int pthread_key_create(pthread_key_t *key, // 返回值,当前没有被使用的标志位
void (*destructor)(void *));// 析构函数指针
void *pthread_getspecific(pthread_key_t key); // 获取线程特殊数据
int pthread_setspecific(pthread_key_t key, const void *value); // 设置线程特殊数据
多线程版readline
#include "unpthread.h"
static pthread_key_t rl_key;
static pthread_once_t rl_once = PTHREAD_ONCE_INIT;
// 释放线程特定数据
static void readline_destructor(void *ptr) { free(ptr); }
static void readline_once(void) {
Pthread_key_create(&rl_key, readline_destructor);
}
// readline 的线程特定数据结构:缓冲区、缓冲区中的字符数、缓冲区中的下一个字符
typedef struct {
int rl_cnt;
char *rl_bufptr;
char rl_buf[MAXLINE];
} Rline;
static ssize_t my_read(Rline *tsd, int fd, char *ptr) {
if (tsd->rl_cnt <= 0) {
again:
if ((tsd->rl_cnt = read(fd, tsd->rl_buf, MAXLINE)) < 0) {
if (errno == EINTR) {
goto again;
}
return (-1);
} else if (tsd->rl_cnt == 0)
return (0);
tsd->rl_bufptr = tsd->rl_buf;
}
tsd->rl_cnt--;
*ptr = *tsd->rl_bufptr++;
return (1);
}
ssize_t readline(int fd, void *vptr, size_t maxlen) {
size_t n, rc;
char c, *ptr;
Rline *tsd;
// 每个线程都有一个独有的缓冲区
Pthread_once(&rl_once, readline_once);
if ((tsd = pthread_getspecific(rl_key)) == NULL) {
tsd = Calloc(1, sizeof(Rline));
Pthread_setspecific(rl_key, tsd);
}
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ((rc = my_read(tsd, fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n') break;
} else if (rc == 0) {
*ptr = 0;
return (n - 1);
} else
return (-1);
}
*ptr = 0;
return (n);
}
// 对外暴露的函数,用于从文件描述符中读取一行
ssize_t Readline(int fd, void *ptr, size_t maxlen) {
ssize_t n;
if ((n = readline(fd, ptr, maxlen)) < 0) err_sys("readline error");
return (n);
}