一.线程和进程的关系
为什么要引入线程?
CPU的执行速度过快,只有寄存器仅仅可以追上,RAM和别的设备更是难以追上,而进程和线程只不过是对应的CPU时间段的描述.
进程是cpu资源分配的最小单位,线程是cpu调度的最小单位
线程和进程的联系有
(1)进程是资源的分配和调度的一个独立单元,线程是CPU调度的基本单元
(2)同一个进程中包括多个线程,所有的线程共享进程的地址空间,可以在执行过程中节省时间
(3)系统调度时,切换的速度十分快,主要是因为进程地址空间独立而进程的线程独享地址空间
(5)线程不能独立存在,必须依附于进程
(6)进程所使用的内存地址可以限定使用通过信号来控制
线程的优点:
(1)可以提高应用程序的响应速度
(2)可以提高多处理器的效率
(3)可以改善程序的结构
线程可以独立有自己的数据信息
(1)通过使用互斥锁来保存
(2)线程号
(3)堆栈
(4)信号掩码
(5)优先级
(6)线程私有的存储空间
二.创建线程
1.pthread_create - 线程创建
头文件:
#include < pthread.h > int pthread_create
(pthread_t * thread , const pthread_attr_t * attr , void *(* start_routine )(void*), void * arg );
#include < pthread.h > int pthread_create(pthread_t * thread , const pthread_attr_t * attr , void *(* start_routine )(void*), void * arg );
pthread_create()函数用于在进程内创建具有由attr指定的属性的新线程。 如果attr为NULL,则使用默认属性。 如果稍后修改attr指定的属性,则不会影响线程的属性。 成功完成后, pthread_create()将创建的线程的ID存储在线程引用的位置。
创建线程执行start_routine , arg作为其唯一参数。 如果start_routine返回,则效果就像是使用start_routine的返回值作为退出状态对pthread_exit()进行隐式调用。 请注意,最初调用main()的线程与此不同。 当它从main()返回时,效果就好像使用main()的返回值作为退出状态对exit()进行了隐式调用。
新线程的信号状态初始化如下:
信号掩码继承自创建线程。
待处理新线程的信号集为空。
如果pthread_create()失败,则不会创建新线程,并且未定义线程引用的位置的内容。
返回值
如果成功,则pthread_create()函数返回零。 否则,返回错误号以指示错误。
错误
如果出现以下情况, pthread_create()函数将失败:
[EAGAIN]
系统缺少创建另一个线程所需的资源,或者会超出系统对进程PTHREAD_THREADS_MAX中线程总数的限制。
[EINVAL]
attr指定的值无效。
[EPERM]
调用者没有适当的权限来设置所需的调度参数或调度策略。
pthread_create()函数不会返回[EINTR]的错误代码。
创建线程的其他系统的函数
函数 | 说明 |
---|---|
pthread_t pthreadself(void) | 获取本线程的线程ID |
int pthread_equal(pthread_t thread1,pthread_t thread2) | 判断两个进程ID是不是指向同一进程 |
int pthread_once(pthread_once_t * once_control,void(*init_routine)(void) | 用来保证线程函数在进程中只执行一次 |
如何正确的创建线程?
#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)
{
exit(1);
}
sleep(1);//让主线程等待子线程结束,不然无法打印相应的值
return 0;
}
注:在这里sleep是必要的,如果不是用sleep的话,主线程也就是main函数会直接退出,主线程的退出引起了进程的结束,从而导致了子线程的结束,看不到第二次的printf的值
三.线程属性
1.pthread_attr_t详解
typedef struct
{
int detachstatte;
int schedpolicy;
struct sched_param schedparam;
int inheritsched;
int scope;
size_t guardsize;
int stackaddr_set;
void * stackaddr;
size_t stacksize;
}pthread_attr_t;
- detachstata 表示新创建的线程是不是与进程中的其他的线程脱离同步,它的缺省属性是PTHREAD_CREATE_JOINABLE
可以使用pthread_detach()来设置,设置pthread_detach()之后线程运行结束后会自动释放系统状态 - schedpolicy表示新线程的调度策略,主要有SCHED_other(正常,非实时),(SCHED_RR)表示轮转法,SCHED_FIFO(实时,先入先出)
- schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。
- inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
- scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
- guardsize: 警戒堆栈的大小
- stackaddr_set: 堆栈地址集
- stackaddr : 堆栈的地址
- stacksize :堆栈大小
线程终止:
Linux下有三种线程终止的方式:
(1)通过return 从线程函数返回
(2) 通过调用函数pthread_exit()让线程退出
(3)甲线程可以让乙线程使用pthread_cancel()函数从而让它退出
pthread_exit在pthread.h中声明
#include < pthread.h>
void pthread_exit(void * retval)
在主线程中,如果从main函数返回或者是调用了exit函数退出主线程,整个进程将终止,另外一种情况是如果主线程调用了pthread_t函数,仅仅是主函数消亡,进程并不会结束,直到所有的线程结束,进程才会结束
那么线程终止之后的资源释放的问题该如何处理?
系统为我们提供了一对函数,pthread_cleanup_push(),pthread_cleanup_pop()用于自动释放资源
他们是以宏的方式进行提供的,也就是说明了
在push和pop之间不能使用return 函数,不然将不会清理完毕
cleanup_push ()把东西压入栈中,pop把栈中的东西弹出
# define pthread_cleanup_push(routine, arg) \
do { \
__pthread_cleanup_class __clframe (routine, arg)
/* Remove a cleanup handler installed by the matching pthread_cleanup_push.
If EXECUTE is non-zero, the handler function is called. */
# define pthread_cleanup_pop(execute) \
__clframe.__setdoit (execute); \
} while (0)
线程的同步问题:
一般来说,进程中的各个线程的运行是相互独立的,线程的终结并不会相互通知,也不会影响其他线程,终结的线程所占用的资源不会随着线程的结束而归还系统,有些资源得不到释放
和进程中类似的是进程中有wait函数,线程中有pthread_join函数
int pthread_join (pthread_t __th, void **__thread_return)
需要注意的是一个线程只允许一个线程使用pthread_join()等待它的终止,而且被等待的线程应该处于可join状态,即非DETACHED状态,DETACHED状态是指堆某个线程执行了pthread_deatch()函数所处的状态,处于DETACHED状态的线程无法由pthread_join()同步
一个可”join”的线程所占用的内存仅仅当由线程执行了pthread_join之后
四.私有数据及线程同步
线程的私有数据是通过一键多值来实行的,一个键对应多个值
#include<pthread.h>
int pthread_key_create (pthread_key_t *__key,
void (*__destr_function) (void *))
__THROW __nonnull ((1));
/* Destroy KEY. */
int pthread_key_delete (pthread_key_t __key) __THROW;
/* Return current value of the thread-specific data slot identified by KEY. */
void *pthread_getspecific (pthread_key_t __key) __THROW;
/* Store POINTER in the thread-specific data slot identified by KEY. */
int pthread_setspecific (pthread_key_t __key,
const void *__pointer) __THROW ;
- pthread_key_create: 从LInux中的TSD中分配一项,将值给予key供以后使用,key表示的是指向键值的指针,第二个参数为一个函数指针,如果指针不为空,在线程退出之后将以key所关联的数据为参数调用destr_function(),释放缓存区
- pthread_setspecific:通过将pointer于key相关联,指定一个新数据,会同时释放旧数据
- pthread_getspecific: 通过函数的得到于key相关联的数据
- pthread_key_delete :函数用来删除一个键,删除后,键所占用的内存会被释放,但是线程数据的释放必须在释放键之前完成
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
pthread_key_t key;
void * thread2(void * arg)
{
int tsd=5;
printf("thread %d is running\n",pthread_self());//获取pid
pthread_setspecific(key,(int *)tsd);//给予了一个特殊的建位
printf("thread %d returns %d\n",pthread_self(),pthread_getspecific(key));//getspecific函数获取私有数据的键值
}
void * thread1(void * arg)
{
int tsd=0;
pthread_t thid2;
printf("thread %d is running\n",pthread_self());//获取pid
pthread_setspecific(key,(int *)tsd);//给予特殊的建位这个建位为0
pthread_create (&thid2,NULL,thread2,NULL);
sleep(1);
printf("thread %d returns %d\n",pthread_self(),pthread_getspecific(key));
}
int main(void)
{
pthread_t thid1;
printf("main thread begins runnning \n");
pthread_key_create (&key,NULL);//创建一个建值,第二个参数如果不为空的话,线程退出之后
pthread_create (&thid1,NULL,thread1,NULL);//创建一个线程,同时调用thread1线程函数
sleep(3);
pthread_key_delete (key);
printf("main thread exit\n");
return 0;
}
2.互斥锁的使用
函数 | 功能 |
---|---|
pthread_mutex_init 函数 | 初始化一个互斥锁 |
pthread_mutex_destory 函数 | 注销一个互斥锁 |
pthread_mutex_lock 函数 | 加锁,如果不成功,阻塞等待 |
pthread_mutex_unlock 函数 | 解锁 |
pthread_mutex_trylock 函数 | 测试加锁,如果不成功就返回 |
互斥锁的初始化
“静态赋值法: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
通过函数来进行初始化操作
int pthread_mutex_intit (pthread_mutex_t * mutex, const pthread_mutexattr_t *mutexattr);
加锁的同时,如果mutex已经被锁主,当前尝试加锁的线程就会阻塞,直到互斥锁被其他线程释放,当pthread_mutex_lock返回的时候,说明,互斥锁已经被当前线程成功的加锁,pthread_mutex_trylock函数不同,会返回一个EBUSY
int pthread_mutex_unlock(pthread_mutex_t * mutex);
解锁必须满足两个条件,一是互斥锁必须处于加锁状态,二是线程必须是给互斥锁加锁的线程
当一个互斥锁使用完毕之后,必须进行清除,使用函数pthread-mutex_destroy函数
int pthread_mutex_destory(pthread_mutex_t * mutex);
清除的时候要求当前必须处于开放状态,如果锁定状态,返回EBUSY
3条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制
主要包括两个动作:一是等待使用资源的线程等待”条件变量被设置真”,另一个是一个线程在使用完资源后,”设置条件为真”
条件变量可以让我们睡眠到某种状态的出现
pthread_cond_wait与pthread_mutex配合使用
函数 | 功能 |
---|---|
pthread-cond_init 函数 | 初始化变量 |
pthread_cond_wait 函数 | 基于条件变量阻塞,无条件等待 |
pthread-cond_timedwait 函数 | 阻塞直到指定时间发生 |
pthread_cond_singal 函数 | 接触特定线程的阻塞,存在多个等待线程按入队顺序激活其中一个 |
条件变量的初始化方式也有两种
“静态赋值法: pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
通过函数来进行初始化操作
int pthread_cond_ntit (pthread_cond_t * cond, pthread_condattr_t * cond_attr);
等待条件成立的函数有两种:
int pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)
int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex,
const struct timespec *__restrict __abstime)
pthread_cond_wait中的mutex必须为适应锁或者互斥锁
pthread_cont_wait返回自动对互斥锁重新进行加锁
销毁条件变量释放空间,必须在没有条件变量的等待的基础之上
pthread_cond_wait函数释放由mutex指向的互斥锁,同时让当前进程关于cond指向的条件变量阻塞,直到条件被信号唤醒
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
pthread_mutex_t mutex;//互斥锁
pthread_cond_t cond;//操作的条件变量
void * thread1(void * arg)
{
pthread_cleanup_push (pthread_mutex_unlock,&mutex);//用于自动释放资源,pthread_clean_up_push()的调用点和pthread_clean_pop()的调用点
while(1)
{
printf("thread1 is running\n");
pthread_mutex_lock (&mutex);//建立互斥锁,
//无论那种类型的锁,使用pthread_mutex_lock加锁时候,如果mutex被锁住,目前尝试加锁的线程就会阻塞,
//其中一个必须等待解索,等待队列中的第一个线程将会获得互斥锁
pthread_cond_wait (&cond,&mutex);//释放mutex指向的互斥锁,同时让当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒
//线程改变条件变量的值,条件变量会获得一个信号,使得等待条件变量的线程退出阻塞状态
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);//实行互斥锁
pthread_cond_wait(&cond,&mutex);//防止多个线程请求
//基于条件等待,释放互斥锁
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);//
sleep(1);
}
}
int main(void)
{
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(10);
pthread_exit(0);
}