线程:可以理解为轻量级的CPU执行单元。其必须依托在进程内部。
同一个进程里面的多个线程是共享进程的资源的。但是线程也有自己的私有数据,包括:
- 线程号(thread ID)
- 寄存器
- 堆栈
- 信号掩码
- 优先级
- 线程私有存储空间
线程的头文件为pthread.h 链接库为libpthread.a
一、创建线程
线程常用函数
函数 | 说明 |
---|---|
pthread_t pthread_self(void) | 获取本线程的线程ID |
int pthread_equal(pthread_t thread1,pthread_t thread2) | 判断两个线程ID是否指向同一个线程 |
int pthread_once(pthread_once_t *once_control,void(*int_routine)(void)) | 用来保证init_routine线程函数在进程中仅执行一次 |
测试程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int* thread(void *arg)
{
pthread_t newthid;
newthid = pthread_self();
printf("this is a new thread ,thread ID =%u\n",newthid);
return NULL;
}
int main(void)
{
pthread_t thid;
printf("main thread ,ID is %u\n",pthread_self());//打印主线程的ID
if(pthread_create(&thid,NULL, (void *)thread,NULL)!=0)
{
printf("thread creation failed \n");
exit(1);
}
sleep(1);
exit(0);
}
gcc在编译的时候,不指定库,会报错
原因
由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:
gcc -o pthread -lpthread pthread.c
一个有趣的现象
我在程序中死循环创建线程,且线程不退出,发现,CPU的使用率确实很高,但是整个系统并没有卡死的现象。
二、线程属性
参数pthread_attr_t结构体的具体含义
datachstate:表示创建的线程是否与进程中的其他线程脱离同步。
schedpolicy:新线程调度策略
schedparam:调度涉及到的一个结构体(sched_param)
inheritsched:调度是否用继承的。
scop:线程间的CPU竞争范围
guardsize:警戒堆栈大小
stackaddr_set:堆栈地址集
stacksize:堆栈的大小
三、线程终止
使用pthread_exit()
有两种情况会导致线程退出:1.main函数return或exit了。2.主线程调用了pthread_exit。
线程终止,需要关注两个大问题:
1.临界资源(就是资源在一个时间内只有一个线程能用,想用的线程只能等待。)。在终止前,需要做好释放工作。具体如下:
linux系统提供了一对函数:pthread_cleanup_push()和pthread_cleanup_pop()用于自动释放资源。在使用时,这两个函数必须成对使用,且在同一段代码内部。(原因是这两个函数的本质其实是一段宏代码)。
2.线程同步问题。线程中也有类似进程的wait函数,其作用就是等待某一个线程结束了自己才结束。对应的函数是pthread_join()。需要注意
- 被等待的线程不能处于DETACHED状态(既调用pthread_detach函数)。
- 一个线程不能被多个线程等待。否则出了第一个会正常收到返回,其他都会返回错误代码ESRCH。
示例代码:
#include <stdio.h>
#include <pthread.h>
void assisthread(void *arg)
{
printf("I am helping to do some jobs\n");
sleep(3);
pthread_exit(0);
}
int main(void)
{
pthread_t assistthid;
int status;
pthread_create(&assistthid,NULL ,(void *)assisthread,NULL);
pthread_join(assistthid,(void*)&status);
printf("assistthread's exit is cause %d\n",status);
return 0;
}
运行结果:
gcc -o jointhread jointhread.c -lpthread #编译
svauto@ubuntua:~/C/thread$ ./jointhread #运行
I am helping to do some jobs
assistthread's exit is cause 0
程序可以看出主线程在执行pthread_join后处于等待状态,知道子线程退出后,才退出主线程。
四、私有数据
在多线程中,进程的数据空间是共享的,有时候线程需要有自己的私有全局数据,应该怎么做呢?
线程私有数据使用:“一键多值”技术。创建“键”的时候,Linux从TSD池中分配一项,当一个键被创建后,所有的线程都可以访问,但是不同线程从这个键中取到的值是不一样的。这就是一键多值的含义。
涉及到的函数有四个
函数 | 作用 |
---|---|
pthread_key_create | 创建一个键 |
pthread_setspecific | 为一个键设置线程私有数据 |
pthread_getspecific | 从一个键获取私有数据 |
pthread_key_delete | 删除一个键 |
实例代码:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
pthread_key_t key;
void * thread2(void *arg)
{
int tsd=5;
printf("thread %x is running \n",pthread_self());
pthread_setspecific(key,(void *)tsd);
printf("thread %x returns %d \n",pthread_self(),pthread_getspecific(key));
}
void *thread1(void *arg)
{
int tsd = 0;
pthread_t thid2;
printf("thread %x is running \n",pthread_self());
pthread_setspecific(key,(void *)tsd);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(2);
printf("thread %x returns %d\n",pthread_self(), pthread_getspecific(key));
}
int main(void)
{
pthread_t thid1;
printf("main thread begins running\n");
pthread_key_create(&key,NULL);
pthread_create(&thid1,NULL,thread1,NULL);
sleep(3);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
运行结果:
可以看出两个线程的私有数据,是不会互相影响的。
五、线程同步
linux有3种方式处理线程的同步:
- 互斥锁
- 条件变量
- 异步信号
1.互斥锁
在同一时刻,通常只允许一个线程执行一个关键部分的代码。涉及到的函数如下
函数 | 功能 |
---|---|
pthread_mutex_init | 初始化一个互斥锁 |
pthread_mutex_destroy | 注销一个互斥锁 |
pthread_mutex_lock | 加锁,如果不成功则阻塞等待 |
pthread_mutex_unlock | 解锁 |
pthread_mutex_trylock | 测试加锁,如果不成功则立即返回,错误码为EBUSY |
互斥锁初始化
初始化方法有两种
实例代码
下面代码用互斥锁的方式,保护全局变量在各线程之间同步(注意:此时不能在代码中直接操作全局变量,而应该调用下面的read ,write函数)
#include <stdio.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t number_mutex;
int globalnumber;
void write_globalnumber()
{
pthread_mutex_lock(&number_mutex);
globalnumber++;
printf("now globalnumber=%d\n",globalnumber);
pthread_mutex_unlock(&number_mutex);
}
int read_globalnumber()
{
int temp;
pthread_mutex_lock(&number_mutex);
temp= globalnumber;
pthread_mutex_unlock(&number_mutex);
return (temp);
}
void * thread2(void *arg)
{
int tsd=5,i=20;
printf("thread2 %x is running \n",pthread_self());
while(i>0)
{
printf("read =%d\n",read_globalnumber());
i--;
usleep(1);
}
}
void *thread1(void *arg)
{
int tsd = 0,i=10;
pthread_t thid2;
printf("thread1 %x is running \n",pthread_self());
while(i>0)
{
write_globalnumber();
i--;
usleep(1);
}
}
int main(void)
{
pthread_t thid1,thid2;
int statue;
printf("main thread begins running\n");
pthread_create(&thid1,NULL,thread1,NULL);
pthread_create(&thid2,NULL,thread2,NULL);
pthread_join(thid1,&statue);
printf("statue1=%d\n",statue);
pthread_join(thid2,&statue);
printf("statue2=%d\n",statue);
printf("main thread exit\n");
return 0;
}
程序运行结果:
每次运行的会不一样。因为线程的调度是受系统控制的,不是按顺序调度的。一个有趣的现象是
当数据变量是3,为什么输出是2呢?因为读线程正准备打印2的时候被中断了,然后执行了写线程写入3。所以,互斥锁做的同步是保证加锁的部分所用到的资源,在同一时刻只有一个线程能用到。如果把输出语句放到加锁的部分,就可以看到读出来的和写出来的,是按顺序出来的。
2.条件变量
测试代码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void * thread1(void *arg)
{
pthread_cleanup_push(pthread_mutex_unlock,&mutex);
while(1)
{
printf("thread1 is running\n");
pthread_mutex_lock(&mutex);
printf("thread1 mutex\n");
pthread_cond_wait(&cond,&mutex);
printf("thread1 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(4);
}
pthread_cleanup_pop(0);
}
void *thread2(void *arg)
{
while(1)
{
printf("thread2 is running\n");
pthread_mutex_lock(&mutex);
printf("thread2 mutex\n");
pthread_cond_wait(&cond,&mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t tid1,tid2;
printf("condition variable study\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&tid1,NULL,(void *)thread1,NULL);
pthread_create(&tid2,NULL,(void *)thread2,NULL);
do{
pthread_cond_signal(&cond);
}while(1);
sleep(50);
pthread_exit(0);
}
上面代码加了“pthread_cleanup_push(pthread_mutex_unlock,&mutex);”和“pthread_cleanup_pop(0);”这对函数,目的是解决线程结束时资源释放的问题。因为pthread_cond_wait函数属于“取消点”。其实对于thread2也应该设置这对函数。他们在线程出现意外的时候,才会使用到。具体,请看我另一篇关于“线程终止的博文”
3.异步信号
六、错误码
错误码定义在“error.h”以E开头的宏定义。
常见的错误码
错误码 | 含义 |
---|---|
ENOMEM | 内存不足 |
EIO | 输入输出错误 |