私有数据:
在多线程环境下,进程内的所有线程共享进程的数据空间,因此全局变量为所有线程共有。
而有时候我们需要保存线程自己的全局变量,这种特殊的变量仅在某个线程内部有效。
可以通过创建线程的私有数据来解决:在线程内部,私有数据可以被各个函数访问,但对其他线程是屏蔽的。
线程的私有数据采用了—— 一键多值 的技术,即一个键对应多个数值,访问数据时通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据,
使用线程私有数据时,首先要为每个线程数据创建一个相关联的键,在各个线程内部,都使用这个公用的键来指代线程数据。但在不同的线程中,这个键所代表的数据是不同的。
pthread_key_create ( pthread_key_t key ,void ( *destr_function )( void ) ) :
从linux 的TSD池中分配一项,将其值赋给key供以后访问使用,第一个参数为指向键值的指针,第二个参数为一个函数指针。key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值。
pthread_setspecific ( pthread_key_t key,const void *pointer )
该函数将pointer的值(不是内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须先释放原有的线程数据用以回收空间。
pthread_getspecific ( pthread_key_t key ):
通过该函数得到一个与key相关联的数据
pthread_key_delete ( pthread_key_t key) :
该函数用来删除一个键,删除后,键所占用的内存将被释放,需要注意的是,键占用的内存被释放,但是与键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。
代码:
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
pthread_key_t key;
void * thread2(void *arg)
{
int tsd =5;
printf("线程2 %d 正在运行\n",pthread_self());
pthread_setspecific(key,(void *)tsd); //为线程2分配键值5
printf("线程2 %d 的键值为:%d\n",pthread_self(),pthread_getspecific(key));
}
void * thread1(void *arg)
{
int tsd=0;
pthread_t thid2;
printf("线程1 %d 正在运行\n",pthread_self());
pthread_setspecific(key,(void *)tsd); //为线程1分配键值
pthread_create(&thid2,NULL,thread2,NULL); //创建线程2.运行函数
sleep(4);
printf("线程1 %d 的键值为:%d\n",pthread_self(),pthread_getspecific(key));
}
int main()
{
pthread_t thid1;
printf("主线程正在运行:\n");
pthread_key_create (&key,NULL); //创建键
pthread_create (&thid1,NULL,thread1,NULL); //创建新线程
sleep(8);
pthread_key_delete(key);
printf("主进程结束\n");
return 0;
}
//要注意主线程和线程1之间的休眠sleep()关系,如果主线程的休眠时间结束,那么直接回到主线程
线程同步:
1.互斥锁
互斥锁通过锁机制来实现线程间的同步。在同一时刻它通常只允许一个线程执行一个关键部分的代码
初始化
pthread_mutex_init初始化一个互斥锁:两种方式:
1 .pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2 .pthread_mutex_init ( pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);加锁
pthread_mutex_lock ( pthread_mutex_t *mutex );
pthread_mutex_trylock ( pthread_mutex_t *mutex);使用第一个函数加锁时,如果mutex已经被锁住,当前尝试加锁的线程就会阻塞,直到互斥锁被其他线程释放。当函数返回时,说明已经加锁成功。而第二个函数,如果mutex已经被锁住,会立即返回,返回的错误EBUSY.
解锁
pthread_mutex_unlock ( pthread_mutex_t *mutex);解锁函数在使用时必须满足:1.互斥锁处于加锁状态。2.调用本函数的线程必须是给互斥锁加锁的线程,解锁后若有其他线程在等待互斥锁,等待队列中的第一个线程将获得互斥锁。
清除锁
pthread_mutex_destory ( pthread_mutex_t *mutex );清除一个互斥锁意味着释放它所占的资源。清除锁时要求锁处于已解锁状态。若锁处于锁定状态,函数返回EBUSY,成功执行则返回0。
2.条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制。
主要包括:一个等待使用资源的线程等待“条件变量被设置为真”;另一个线程在使用完资源后“设置条件为真”。
为了保证条件变量可以正确的被修改,使用中需要用到互斥锁。
初始化:
两种方法:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_init ( pthread_cond_t * cond,pthread_condattr_t * cond_attr );cond_attr参数是条件变量的属性,由于其并没有得到实现,所以值通常是NULL;
等待条件成立:
pthread_cond_wait ( pthread_cond_t * cond,pthread_mutex_t *mutex );
pthread_cond_timewait( pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime );wait函数释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒。通常条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获得一个信号,使得等待条件变量的线程退出阻塞状态。
条件变量激活:
pthread_cond_signal ( pthread_cond_t *cond );
pthread_cond_broadcast ( pthread_cond_t *cond );signal激活一个等待条件成立的线程,存在多个等待线程时,按入队顺序激活其中一个; broadcast则激活所有等待的线程。
清除条件变量
pthread_cond_destory ( pthread_cond_t * cond );只有在没有线程等待该条件变量的时候才能清除这个条件变量,否则返回EBUSY
代码:
#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("线程1正在运行\n");
pthread_mutex_lock(&mutex); //设置线程1互斥锁
pthread_cond_wait(&cond,&mutex); //
printf("线程应用条件:\n");
pthread_mutex_unlock(&mutex); //解锁
sleep(4);
}
pthread_cleanup_pop(0);
}
void *thread2(void *arg)
{
while(1)
{
printf("线程2正在运行\n");
pthread_mutex_lock(&mutex); //设置线程2互斥锁
pthread_cond_wait(&cond,&mutex); //
printf("线程2应用条件:\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t tid1,tid2;
printf("条件变量学习\n");
pthread_mutex_init(&mutex,NULL); //初始化一个互斥锁
pthread_cond_init(&cond,NULL); //初始化一个条件变量
pthread_create(&tid1,NULL,(void *)thread1,NULL); //创建新的线程1
pthread_create(&tid2,NULL,(void *)thread2,NULL); //创建新的线程2
do {
pthread_cond_signal(&cond);
} while(1);
sleep(50);
pthread_exit(0);
}
3.异步信号
线程是在内核外实现的。信号与任何线程都是异步的,也就是说信号到达线程的时间是不定的。
如果有多个线程可以接收异步信号,则只有一个被选中。如果并发的多个同样的信号被送到一个进程,每一个将被不同的线程处理。如果所有的线程都屏蔽该信号,则这些信号将被挂起,直到有信号解除屏蔽来处理他们。
出错处理
1.错误检查:
函数执行失败时,一般都会返回一个特定的值,但错误原因并没有说明。
头文件errno.h中的变量errno
程序开始执行时,变量errno被初始化为0,许多库函数在执行过程中遇到错误时就会将它设置为相应的错误码。
代码:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
FILE *fd;
char * filename = "test";
errno = 0; //错误变量初始化为0
fd =fopen(filename,"r");
if(fd=NULL)
{
printf("打开文件 %s 失败,错误是 %d\n",filename,errno);
}
else
{
printf("打开文件 %s 成功\n",filename);
}
}
2.错误的提示信息
当程序出现错误时,可以打印出相应的错误提示信息,以便用户修改错误。
函数strerror和perror可以通过错误码获取标准的错误提示信息
* streeor ( int errnum ):
函数根据参数errnum提供的错误码获取一个描述错误信息的字符串。errnum 的值通常就是 errno.
* perror ( const char *message ):
打印错误信息到屏幕或命令行终端,如果参数message是一个空指针,perror仅仅根据errno打印出对应的错误提示信息。
如果提供一个非空的值,peeor会把message加在其输出信息的前面,会添加一个冒号和空格将message和错误信息分开。
代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
FILE *open_file(char *filename)
{
FILE *stream;
errno=0;
stream=fopen(filename,"r");
if(stream==NULL)
{
printf("不能打开文件:%s\n,原因是:%s\n",filename,strerror(errno));
exit(-1);
}
else
{
return stream;
}
}
int main()
{
char *filename = "test1";
open_file(filename);
return 0;
}