目录:
- 线程种类
- 线程的创建
- 对线程的管理
- 线程的属性
- 线程之间的互斥
1、线程种类
– 内核态线程
(1)由内核调度程序直接调度,充分发挥多处理器的优势
(2)目前linux系统标准线程库采用内核线程方式实现多线程
– 用户态线程
(1)一个进程包含多个线程,这些线程从内核调度角度来看只是一个进程,内核把它当一个进程来调度。线程之间调度在用户态进行
(2)用户态线程优点是调度效率高(不需要内核参与调度),缺点是对于多核处理器利用率不高,一个线程阻塞会导致整个线程组阻塞
– 区分内核态线程与用户态线程
如果CPU为4核心,此时内核态线程有4个,那么CPU的4个核心分别执行一个线程,CPU的利用率最高,执行效率也最快,但是开始运行的时候线程会从用户态切换到内核态,运行完成线程后又从从内核态切换到用户态,这会消耗其他的资源,浪费时间。
如果此时用户态线程有4个,运行时操作系统只会给这个进程分配一个CPU核心数(进程是资源分配的最小单位),此时进程内的4个线程共用这一个CPU核心数,在进程内它们根据优先级的大小来轮流抢占这个进程的时间片实现“同步”,微观上讲是有间隔的,内核线程才是真正的同步,每个线程都运行在自己的CPU核心上,不用抢占时间片。由于用户态线程是运行在进程内部的,所以操作系统是不知道用户态线程的,它只知道这一个进程(这与线程是CPU调度的最小单位不矛盾)。
2、线程的创建
pthread_create
– #include<pthread.h>
– int pthread_create(pthread_t *id, pthread_attr_t *attr, void *pFun, void *args)
• id : 返回线程ID
• attr: 线程属性,无属性设置填NULL
• pFun: 线程调用的函数
• args: 线程函数的参数
Demo
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void* ThreadFunc(void* param)
{
int n = *((int*)param);
int iRet = 0;
int i;
for (i = 0; i < n; i++)
{
iRet += i;
fprintf(stderr, "thread[%d]->%d\n", n, iRet);
usleep(100);
}
printf("\n");
return NULL;
}
int main()
{
pthread_t thread_id;
int num = 5;
pthread_create(&thread_id, NULL, ThreadFunc, (void*)&num);
pthread_join(thread_id, NULL);
return 0;
}
3、对线程的管理
Function
pthread_self()
功能:线程函数里,获取本线程的线程ID
int pthread_join(pthread_t id, void **pRet)
功能:使主进程等待线程完成后才退出
pRet: 获得 pthread_exit 函数调用的返回值
pthread_exit(void *pRet)
pRet:指定线程退出返回值
Demo
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int g_pRet;
void* thread()
{
g_pRet = 100;
pthread_exit(&g_pRet);
return NULL;
}
int main()
{
pthread_t tId;
int iRet;
iRet = pthread_create(&tId, NULL, thread, NULL);
if (iRet)
{
perror("Fail to pthread_create!");
return iRet;
}
int *pRet;
pthread_join(tId, (void*)&pRet);
printf("thread exit code:pRet = %d\n", *pRet);
return 0;
}
4、线程的属性
线程属性结构体
struct pthread_attr_t {
int __detachstate;
int __schedpolicy;
struct sched_param __schedparam;
int __inheritsched;
int __scope;
size_t __guardsize;
int __stackaddr_set;
void * __stackaddr;
unsigned long int __stacksize;
__detachstate:可分离属性(是否与进程中其他线程脱离同步)
__schedpolicy:调度策略
__schedparam:线程优先级
__inheritsched:继承属性
__scope:线程种类(内核态、用户态)
__guardsize:警戒缓冲区,防止桟溢出的扩展内存大小(防止一定程度的缓冲区溢出造成的程序崩溃)
__stackaddr_set:
__stackaddr:堆栈地址
__stacksize:堆栈的大小
Function
pthread_getattr_np(pthread_t id, pthread_attr_t *attr)
功能:获取线程属性
pthread_attr_init(pthread_attr_t *attr)
功能:初始化线程属性(初始化的为默认属性)
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *pdetachstate)
功能:设置/获取线程分离状态
• PTHREAD_CREATE_JOINABLE 线程可与其他线程同步(默认),此时pthread_join()才会生效
• PTHREAD_CREATE_DETACHED 线程的资源在退出后自行释放
Demo
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void* ThreadFunc(void* param)
{
pthread_attr_t attr;
// 线程函数里,获取本线程的线程ID
pthread_t thread_id = pthread_self();
// 获取线程属性放在attr中
pthread_getattr_np(thread_id, &attr);
int iDetachstate;
// 从attr中获取detach属性
pthread_attr_getdetachstate(&attr, &iDetachstate);
printf("detachstate:%s\n", PTHREAD_CREATE_JOINABLE==iDetachstate?"Joinble":"Detached");
return NULL;
}
int main()
{
//Demo_ThreadAttr();
pthread_attr_t attr;
// 初始化线程属性,初始化的为默认值,只有设置才能改变
pthread_attr_init(&attr);
// 设置线程分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t thread_id;
pthread_create(&thread_id, &attr, (void*)ThreadFunc, NULL);
usleep(1000);
return 0;
}
其他属性
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)
int pthread_attr_getschedpolicy (pthread_attr_t *attr, int *pPolicy)
功能:设置/获取线程调度策略
• SCHED_OTHER 系统默认(分时调度),各个优先级任务时间轮换
• SCHED_FIFO 实时调度,先到先服务(独占)
• SCHED_RR 实时调度,时间片轮转(高优先级轮换)
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched)
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *pinheritsched)
功能:设置/获取线程继承性
• PTHREAD_INHERIT_SCHED 从父进程继承调度属性
• PTHREAD_EXPLICIT_SCHED 不从父进程继承调度属性
int pthread_attr_setscope(pthread_attr_t *attr, int scope)
int pthread_attr_getscope(pthread_attr_t *attr, int *pscope)
功能:设置/获取线程作用域
• PTHREAD_SCOPE_SYSTEM 系统所有进程间调度
• PTHREAD_SCOPE_PROCESS 当前进程间调度
int pthread_attr_setstackaddr(pthread_attr_t *attr, void * stackaddr)
int pthread_attr_getstackaddr(pthread_attr_t *attr, void ** stackaddr)
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t *stacksize)
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t *stacksize)
功能:获取/设置线程桟信息
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize)
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t *guardsize)
功能:设置/获取线程警戒缓冲区
pthread_attr_destroy(pthread_attr_t *attr)
功能:无效化线程属性,无效化后, 使用attr 创建线程会失败
5、线程之间的互斥
多线程共享进程资源,访问共享资源时,需要进行互斥操作确保数据的有效性。此处讲解的为互斥锁实现线程的同步互斥。
互斥锁类型
– mutex 锁类型->PTHREAD_MUTEX_XXX
NORMAL: 普通锁,不提供死锁检测,可能出现死锁
ERRORCHECK: 检错锁,同一线程对已锁的互斥锁加锁会返回错误
RECURSIVE: 嵌套锁,允许同一线程多次锁定而不产生死锁,多次释放才能被别的线程锁定
DEFAULT: 默认为普通锁,排队获取锁
设置/获取锁类型
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);
int pthread_mutexattr_gettype(pthread_mutexattr_t* attr, int *type);
线程互斥方法
– 创建互斥锁
pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
– 互斥锁加锁
pthread_mutex_lock(pthread_mutex_t *mutex)
– 互斥锁解锁
pthread_mutex_unlock(pthread_mutex_t *mutex)
Demo
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
pthread_mutex_t lock;// 声明一个互斥锁
int g_num;
void* ThreadFunc_lock1(void* param)
{
int iRet;
while (g_num > 0)
{
iRet = pthread_mutex_lock(&lock);// 加锁,锁定这段资源(这里共享的g_num)
if (iRet)
{
printf("lock error\n");
usleep(1000);
}
printf("thread1:%d\n", g_num);
g_num--;
pthread_mutex_unlock(&lock);// 解锁
usleep(1);
}
return NULL;
}
void* ThreadFunc_lock2(void* param)
{
int iRet;
while (g_num > 0)
{
iRet = pthread_mutex_lock(&lock);// 加锁,锁定这段资源(这里共享的g_num)
if (iRet)
{
printf("lock error\n");
usleep(1000);
}
printf("thead2:%d\n", g_num);
g_num--;
pthread_mutex_unlock(&lock);// 解锁
usleep(1);
}
return NULL;
}
int main()
{
//pthread_mutex_init(&lock, NULL);// 初始化为默认锁
pthread_mutexattr_t attr;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
// 等价上面两句 lock = PTHREAD_MUTEX_ERRORCHECK;
pthread_mutex_init(&lock, &attr);
pthread_t tid1, tid2;
g_num = 100;
pthread_create(&tid1, NULL, ThreadFunc_lock1, NULL);
pthread_create(&tid2, NULL, ThreadFunc_lock2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
所有示例的makefile
.PHONY:clean all
SRC=$(wildcard *.c) # 找出所有当前目录的.c文件
OBJ=$(SRC:%.c=%.o) # 把SRC所有的.c文件赋值给.o文件
BIN=$(OBJ:%.o=%) # 去掉所有的.o
CC=gcc # CC默认表示gcc
CFLAGS=-g -Wall # gcc 编译参数-g -Wall
LIBS=-lpthread # 依赖的库目录
# $<: 依赖的第一个文件
# $^:所有的依赖文件
# $@:就是目标文件%
# 生成的目标文件是all,目标文件依赖所有的BIN文件
# 此处使用了makefile的自动推导功能,OBJ是.o文件,makefile就会去找怎么生成.o文件
all:clean $(BIN)
$(BIN):%:%.c
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
clean:
rm -rf $(BIN) $(OBJ)