Linux 线程整理-持续更新中
注意事项
可重入,线程安全
全局变量 g
共享资源
保证线程安全,尽量使用局部变量
typedef unsigned long int pthread_t;
用途:pthread_t用于声明线程ID。
sizeof(pthread_t) =8
在使用printf打印时,应转换为lu类型。
线程创建
正常模式启动
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <math.h>
//定义线程运行函数
void *th_fn(void *arg)
{
int distance = (int)arg;
int i;
for (i = 1; i <= distance; i++) {
printf("0x%lx run %d\n", pthread_self(), i);
int time = (int)(drand48() * 100000);
usleep(time); //微妙
}
return (void*)0;
}
int main(int argc, char *argv[])
{
int err;
pthread_t rabbit, turtle; //定义线程标识符
//创建rabbit线程
if ((err = pthread_create(&rabbit, NULL, th_fn, (void*)50)) != 0) {
perror("pthread_create error");
}
//创建turtle线程
if ((err = pthread_create(&turtle, NULL, th_fn, (void*)50)) != 0) {
perror("pthread_create error");
}
//sleep(10);
//主控线程调用pthread_join(),自己会阻塞
//直到rabbit和turtle线程结束方可运行
pthread_join(rabbit, NULL);
pthread_join(turtle, NULL);
printf("control thread id: 0x%lx\n", pthread_self());
printf("finished!\n");
return 0;
}
分离状态启动
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void out_state(pthread_attr_t *attr)
{
int state;
if (pthread_attr_getdetachstate(attr, &state) != 0) {
perror("getdetachstate error");
}
else {
if (state == PTHREAD_CREATE_JOINABLE) {
printf("joinable state\n");
}else if (state == PTHREAD_CREATE_DETACHED) {
printf("detach state\n");
}else {
printf("error state\n");
}
}
}
void *th_fn(void *arg)
{
int i;
int sum = 0;
for (i = 1; i <= 100; i++)
sum += i;
return (void*)sum;
}
int main(int argc, char *argv[])
{
int err;
pthread_t default_th, detach_th;
//定义线程属性
pthread_attr_t attr;
//对线程属性初始化
pthread_attr_init(&attr);
//输出线程属性
out_state(&attr);
//取分离属性的默认值,以正常方式启动子线程
if ((err = pthread_create(&default_th, &attr, th_fn, (void*)0)) != 0) {
perror("pthread_create err");
exit(1);
}
int *res;
if ((err = pthread_join(default_th, (void**)&res)) != 0) {
perror("pthread_join error");
}else {
printf("default return value is %d\n", (int)res);
}
printf("------------------------------------\n");
//设置分离属性为分离状态启动
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
out_state(&attr);
//以分离状态启动子线程
if ((err = pthread_create(&detach_th, &attr, th_fn, (void*)0)) != 0) {
perror("pthread_create err");
exit(1);
}
//销毁线程属性
pthread_attr_destroy(&attr);
sleep(1);
printf("0x%lx finished!\n", pthread_self());
return 0;
}
线程终止
主动终止
return
pthread_exit(void *retval);
pthread_join(pthread_t th, void **thread_return);
被动终止
pthread_cancel(pthread_t tid);
线程可以被同一进程的其它线程取消,tid为被终止的线程id
多线程(互斥、同步)
线程互斥实现方式
互斥锁(量)
数据类型:pthread_mutex_t
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
一般情况下初始化锁时,第二个参数传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);
成功返回0,出错返回出错码
-
互斥锁类型
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
PTHREAD_MUTEX_NORMAL
描述:此类型的互斥锁不会检测死锁。如果线程在不首先解除互斥锁的情况下尝试重新锁定该互斥锁,则会产生死锁。尝试解除由其他线程锁定的互斥锁会产生不确定的行为。如果尝试解除锁定的互斥锁未锁定,则会产生不确定的行为。
PTHREAD_MUTEX_ERRORCHECK
描述:此类型的互斥锁可提供错误检查。如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则会返回错误。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。
PTHREAD_MUTEX_RECURSIVE
描述:如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则可成功锁定该互斥锁。 与 PTHREAD_MUTEX_NORMAL 类型的互斥锁不同,对此类型互斥锁进行重新锁定时不会产生死锁情况。多次锁定互斥锁需要进行相同次数的解除锁定才可以释放该锁,然后其他线程才能获取该互斥锁。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。 如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。
PTHREAD_MUTEX_DEFAULT
描述:如果尝试以递归方式锁定此类型的互斥锁,则会产生不确定的行为。对于不是由调用线程锁定的此类型互斥锁,如果尝试对它解除锁定,则会产生不确定的行为。对于尚未锁定的此类型互斥锁,如果尝试对它解除锁定,也会产生不确定的行为。允许在实现中将该互斥锁映射到其他互斥锁类型之一。对于 Solaris 线程,PTHREAD_PROCESS_DEFAULT 会映射到 PTHREAD_PROCESS_NORMAL。
读写锁
线程使用互斥锁缺乏读并发性
当读操作较多,写操作较少时,可使用读写锁提高线程读并发性
数据类型:pthread_rwlock_t
#include <pthread.h>
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_destroy(&rwlock);
pthread_rwlock_wrlock(&rwlock);
pthread_rwlock_rdlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
读写锁特性:
- 读和读:不排斥
- 读和写/写和读:排斥
- 写和写:排斥
线程同步实现方式
互斥锁的缺点是它只有两种状态:锁定和非锁定。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。
条件变量内部是一个等待队列,放置等待的线程,线程在条件变量上等待和通知,互斥锁用来保护等待队列(对等待队列上锁),条件变量通常和互斥锁一起使用。(因为等待队列是个共享资源)
条件变量允许线程等待特定条件发生,当条件不满足时,线程通常先进入阻塞状态,等待条件发生变化。一旦其它的某个线程改变了条件,可唤醒一个或多个阻塞的线程。
具体的判断条件还需用户给出
条件变量
数据类型
pthread_cond_t
初始化、销毁
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
返回:成功返回0, 出错返回错误编号
条件变量等待操作
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict timeout);
返回:成功返回0, 出错返回错误编号
struct timespec
{
time_t tv_sec;
long tv_nsec;
}
互斥锁mutex是对条件变量cond的保护
线程由于调用wait函数阻塞,否则释放互斥锁
pthread_cond_wait(cond, mutex)
{
1 unlock(&mutex);//释放锁
2 lock(&mutex); //保护等待队列
3 将线程自己插入到条件变量的等待队列中
4 unlock(&mutex);
5 当前等待的线程阻塞 <== 等其它线程通知唤醒(broadcast/signal)
6 在唤醒后,lock(&mutex);
7 从等待队列中删除线程自己
}
条件变量通知操作
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
当条件满足时,线程需要通知等待的线程
读者-写者问题
利用条件变量实现
生产者、消费者问题
线程同步和互斥-线程信号量
数据类型 sem_t
信号量的初始化和销毁
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned value);
int sem_destroy(sem_t *sem);
成功返回0,出错返回错误编号
信号量的加和减操作
#include <semaphore.h>
int sem_post(sem_t *sem); //增加信号量的值 +1
int sem_wait(sem_t *sem); //减少信号量的值 -1
int sem_trywait(sem_t *sem); //sem_wait的非阻塞版本
成功返回0,出错返回错误编号
当线程调用sem_wait()后,若信号量的值小于0则线程阻塞。只有其它线程
在调用sem_post()对信号量作加操作后并且其值大于或等于0时,阻塞的线程才能继续运行
PV操作
P操作:减
V操作:加
sem_post() 加1操作 ====>V(1)
sem_wait() 减1操作 ====>P(1)
信号灯(0或1)
利用线程信号量实现线程的互斥
线程信号量初值为1