您的位置:首页 > 运维架构 > Linux

Linux之线程控制

2017-09-04 21:56 141 查看

线程属性

通过函数设置创建线程时候的属性。



int makethread(void * (*fn)(void *) , void *arg)//创建带有分离属性的进程
{
int err;
pthread_t tid;//线程结构体
pthread_attr_t attr;//线程属性结构体
err = pthread_attr_init(&attr);//默认初始化attr里面的成员,里面需要动态分配内存
if(err != 0)//分配失败
return(err);
err = pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_DETACHED);//给属性结构成员赋值,设置线程分离属性
if(err == 0)//分配失败
err = pthread_create(&tid , &attr , fn , arg);//创建线程
pthread_attr_destroy(&attr);//释放前面的动态分配内存.
return (err);
}


以上是创建一个分离式线程的函数,也就是线程退出,系统自动回收线程资源。不能通过join等待线程返回状态。

线程同步属性

当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。在变量修改时间多于二个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种不一致就会出现。当然,这种行为是与处理器体系结构相关的,但是可移植的程序并不能对使用何种处理器体系结构做出任何假设(不能假设在这个平台上面不存在多余二个周期)。

互斥量及其属性

互斥接口确保同一时间只有一个线程访问数据。互斥量本质是一把锁,在访问共享资源前进行加锁,访问完成之后释放锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。只有将所有线程都设计成遵守相同数据访问规则的,互斥机制才能正常工作。

pthread_mutex_t;
//互斥量系统定义的数据类型。具体内容不需要管,总不是由系统处理的一些标志位
int pthread_mutex_init (pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr);
//互斥量初始系统调用,使用默认属性则设置为NULL,否则传入一个已经初始化好的pthread_mutexattr_t数据类型。
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
//如果动态分配(malloc)互斥量,则可以在释放(free)之前调用destory
int pthread_mutex_lock (pthread_mutex_t *__mutex);
//给互斥量加锁,数据仅当前取得锁的进程使用
int pthread_mutex_trylock (pthread_mutex_t *__mutex);
//试图对数据加锁,如果没有锁,则加锁,如果已经被其他进程锁了,则返回错误(EBUSY),且当前进程不阻塞继续执行。
int pthread_mutex_unlock (pthread_mutex_t *__mutex);
//给互斥量接锁,数据可供其他进程使用

pthread_mutexattr_t;
//互斥量属性结构体。对此赋值,传入上面init函数,可以初始化都有自定义属性的互斥量。属性注意3个,进程共享属性,健壮属性以及类型属性
int pthread_mutexattr_init (pthread_mutexattr_t *__attr);
//按照默认属性初始化,pthread_mutexattr_t数据结构体。
int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr);
//反初始化pthread_mutexattr_t数据结构体。
int pthread_mutexattr_getpshared (const pthread_mutexattr_t *
__restrict __attr,
int *__restrict __pshared);
//得到pthread_mutexattr_t数据类型中的进程共享属性的值。

/*
共享属性:可以使多个进程和线程一样共享数据,那么共享属性的互斥量生效了。
*/
int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr,
int __pshared);
//设置pthread_mutexattr_t数据类型中的进程共享属性的值。

/*
健壮性属性:假如一个线程取得了锁,但是线程退出锁未解开,这种情况下使用互斥量的行为是为定义的(是否取得锁已经不清楚了),为了应对这个问题,提出了健壮性属性。
*/
int pthread_mutexattr_getrobust (const pthread_mutexattr_t *__attr,
int *__robustness);
//得到pthread_mutexattr_t数据类型中的互斥量健壮性的值。
int pthread_mutexattr_getrobust_np (const pthread_mutexattr_t *__attr,
int *__robustness);
//设置pthread_mutexattr_t数据类型中的互斥量健壮性的值。

/*
类型属性:定义在错误使用了锁(锁了没解,没上锁确解锁,已解锁继续解锁)之后,函数返回的错误类型,以及系统处理之后的结果。
*/
int pthread_mutexattr_gettype (const pthread_mutexattr_t *__restrict
__attr, int *__restrict __kind);
//得到pthread_mutexattr_t数据类型中的互斥量类型的值。值不同对应互斥量加锁之后处理稍微不同。
int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind);
//设置pthread_mutexattr_t数据类型中的互斥量类型的值。值不同对应互斥量加锁之后处理稍微不同。


一个示例:

这里写代码片


读写锁(共享互斥锁)及其属性

读–共享,写–互斥。

读写锁 (reader-writer lock) 与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只能有一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程占用写模式的读写锁,但是可以允许多个线程占用读模式的读写锁。读写锁非常适用于读的次数远远大于写的情况。

pthread_rwlock_t;//读写锁数据类型
//初始化读写锁数据以及其属性,属性NULL表示默认
int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,
const pthread_rwlockattr_t *__restrict
__attr);
//释放内存之前,对读写锁进行清理工作。
int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock);
//读模式下锁定读写锁。
int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock);
//写模式下锁定读写锁。
int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock);
//读写模式下进行解锁。
int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock);
//可以获取锁则返回0,锁住读写锁;不可以获取返回EBUSY,进程继续执行。
int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);
//可以获取锁则返回0,锁住读写锁;不可以获取返回EBUSY,进程继续执行。
int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);

//属性初始化默认
int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr);
//属性反初始化
int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr);
//获取进程共享属性
int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *
__restrict __attr,
int *__restrict __pshared);
//设置进程共享属性
int pthread_rwlockattr_setpshared (const pthread_rwlockattr_t *
__restrict __attr,
int *__restrict __pshared);


条件变量及其属性

条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

条件变量,它是发送信号与等待信号。互斥锁用户上锁,条件变量则用于等待。一般来说,在一个进程/线程中调用pthread_cond_wait(..)等待某个条件的成立,此时该进程阻塞在这里,另外一个进程/线程进行某种操作,当某种条件成立时,调用pthread_cond_signal(…)来发送信号,从而使pthread_cond_wait(…)返回。此处要注意的是,这里所谈到的信号,不是系统级别的SIGXXXX信号,只是用信号这个词语更容易理解。条件变量与信号量更接近或者就可以认为是信号量。

其实想一想,pthread_cond_wait函数也可以用一个while死循环来等待条件的成立,但要注意的是,使用while死循环会严重消耗CPU,而pthread_cond_wait则是采用线程睡眠的方式,它是一种等待通知模式,而不是一直的检查模式。

#define PTHREAD_COND_INITIALIZER { { 0, 0, 0, 0, 0, (void *) 0, 0, 0 } }
typedef union
{
struct
{
int __lock;
unsigned int __futex;
__extension__ unsigned long long int __total_seq;
__extension__ unsigned long long int __wakeup_seq;
__extension__ unsigned long long int __woken_seq;
void *__mutex;
unsigned int __nwaiters;
unsigned int __broadcast_seq;
} __data;
char __size[__SIZEOF_PTHREAD_COND_T];
__extension__ long long int __align;
} pthread_cond_t;//此处为什么需要利用枚举?并且还涉及了数据对齐的操作。

//pthread_cond_t初始化位默认属性。
int pthread_cond_init (pthread_cond_t *__restrict __cond,
const pthread_condattr_t *__restrict __cond_attr);
//先利用反初始化,然后再释放条件变量底层内存空间。
int pthread_cond_destroy (pthread_cond_t *__cond);

//条件变为真之后,signal函数通知。给定时间内条件不满足,则返回一个对应错误码。
//进入wait,对互斥量解锁并等待上锁的条件改变,返回之后对互斥量上锁。
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);

//给条件变量发信号,一定要在条件改变之后再给线程发信号。
int pthread_cond_signal (pthread_cond_t *__cond);
pthread_cond_broadcast (pthread_cond_t *__cond);


pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;//初始化互斥量
pthread_cond_t qread = PTHREAD_COND_INITIALIZER;//初始化条件变量
pthread_t tid1 , tid2 , tid3;
int x = 10;
int y = 20;
void *thr_fun1(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
pthread_mutex_lock(&qlock);//给互斥量上锁
while(x < y){
pthread_cond_wait(&qread , &qlock);//等待得到条件满足的通知,此时为了等待其他线程发信号,顾暂时对互斥量解锁.
}
pthread_mutex_unlock(&qlock);//给互斥量解锁
printf("thr_fun1\n");
sleep(1);
pthread_exit((void *)1);
}
void *thr_fun2(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
pthread_mutex_lock(&qlock);//给互斥量上锁.
x = 20;
y = 10;
printf("x and y changed!\n");
pthread_mutex_unlock(&qlock);//给互斥量解锁.
pthread_cond_signal(&qread);//通知等待的条件变量
printf("thr_fun2\n");
pthread_exit((void *)2);

}
void *thr_fun3(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
pthread_join(tid1 , NULL);//等待tid1进程结束
printf("thr_fun3\n");
pthread_exit((void *)2);
}
int main(void)
{
int err;
err = pthread_create(&tid1 , NULL , thr_fun1 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 1");
sleep(1);//故意等待1s再创建第二线程
err = pthread_create(&tid2 , NULL , thr_fun2 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 2");

err = pthread_create(&tid3 , NULL , thr_fun3 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 3");

pthread_join(tid3 , NULL);
printf("mian thread!\n");
exit(0);
}


x and y changed!

thr_fun2

thr_fun1

thr_fun3

mian thread!

运行结果如上所示:

1、 线程1创建之后主线程休眠1s,所以肯定是线程1先运行取得锁然后进入cond函数里面等待条件变量满足通知,因为没有signal函数通知,故卡在此函数里面,并且将互斥量暂时解锁,使得其他线程可以访问共享变量x,y。

2、线程2里面首先给互斥上锁,然后修改共享变量,然后给互斥量解锁。signal告诉线程1,条件x>y已经满足了,你函数可以退出了,线程1从wait退出后,wait又给互斥量上锁。(一定得在发送通知之前将互斥量解锁,因为线程1的wait可能在线程2解锁之前就返回了,返回之后又上锁了,这会导致线程2会卡住。虽然之后线程1解锁之后线程2会继续运行,但是有时可能会出错)。

3、线程3和主线程就不用说明了,通过join一直等待某一个线程结束,再继续运行。

4、在线程或进程间共享数据,需要同步机制,因为我们不清楚系统何时切换线程或者进程。

自旋锁

和互斥量累死,但是不是通过阻塞进程,而是在获取锁之前一直处于忙等(自旋没有休眠)阻塞状态。适用于:锁被持有时间短,而且线程不希望在重新调度上面话费太多成本。

用户层自旋锁不常用,自旋锁通常用于底层实现其他类型的锁。

屏障及其属性

重入

多个线程调用在相同的时间可能调用相同的函数,如果一个这个函数在相同的时间点可以被多个线程安全调用,则该函数是线程安全的。


可重入函数可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static-静态变量),这样的函数就是purecode(纯代码)可重入,可以允许有多个该函数的副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。

也可以这样理解:就是在这函数运行期间,被其他信号中断后,信号处理函数不可能修改这个函数里面的任何变量,或者分配空间。

不可重入原因:使用了静态数据结构或者全局数据结构;调用了malloc或free(因为系统维护一个堆链表,都可以修改);标准IO函数,例如printf函数。

getenv不可重复版本:

#define MAXSTRINGSZ 4096//设定缓存区域大小
static char envbuf[MAXSTRINGSZ];//初始化字符串数组存放环境变量
extern char **environ;//系统提供的变量.因为环境变量是一个一个的指针,所以需要执行指针的指针来读取他们
char *getenv1(const char *name)//不可重入版本
{
int i , len;
len = strlen(name);//求出字符串长度,便于后面利用strcmp比较字符串
for(i = 0 ; environ[i] != NULL ; i++){
if((strncmp(name , environ[i] , len) == 0) && (environ[i][len] == '=')){
strncpy(envbuf , &environ[i][len+1] , MAXSTRINGSZ -1);//把环境变量里面的东西拷贝到备份.不直接返回环境变量系统地址,为了不让外部修改环境变量
return (envbuf);//复制4096,最后肯定对应的都是空字符.
}
}
/*
environ是指向指针的指针,*(environ+i) = environ[i]里面存储一个执行字符串的指针
*(environ[i] + len) = environ[i][len]表示name后面的一个字符
* environ[i][len+1]表示name后面的第二个字符 &environ[i][len+1] = environ[i] + len +1
* 巧妙利用了数组和指针的关系进行字符串的访问.
*/
return (NULL);
}
int main(void)
{
char *temp;
temp = getenv1("GPG_AGENT_INFO");
printf("%s\n" , temp);
exit(0);
}


用到了外部变量,明显这个不是可重入的函数。

getenv可重复版本:



线程特定数据

线程私有数据是存储和查询某个特定线程相关数据的一种机制。让每一个线程可以访问自己单独的数据副本,不需要担心与其他进程同步访问的问题。分配特定数据给进程需要创建与该数据关联的键,通过健获取对特定数据的访问。


在多线程程序中,所有线程共享程序中的变量。现在有一全局变量,所有线程都可以使用它,改变它的值。而如果每个线程希望能单独拥有它,那么就需要使用线程存储了。表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。

//键存储在key中所以需要提前分配,也可以关联一个析构函数。如果退出数据地址非空,则析构函数被调用。创建的键存储在__key指向的内存单元中,这个键可以被多个线程使用,但是每个线程把这个键与不同的线程特定数据地址关联。创建新键时候,每个线程数据地址都是空值。
pthread_key_create (pthread_key_t *__key,
void (*__destr_function) (void *));

//取消键和线程特定数据值直接的关联,并不会激活析构函数
int pthread_key_delete (pthread_key_t __key);

//需要确保给线程分配键并不会由于初始化阶段的竞争发生而发生变动。如果我们进行多次初始化程序就会出现错误。在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态初始化为0,而任何依赖于初始化的代码都能测试该变量。如果变量值仍然为0,则它能实行初始化,然后将变量置为1。以后检查的代码被置为1,那么将跳过初始化。pthread_once函数首先检查控制变量__once_control,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。如果在一个线程初始时,另外的线程调用pthread_once,则调用线程等待,直到那个线程完成初始话返回。__once_control必须是非本地变量(全局或静态)。这样线程键就仅仅被初始化了一次。这些问题都是并发所引发的问题。
int pthread_once (pthread_once_t *__once_control,
void (*__init_routine) (void));

//键创建以后,通过set函数把键和线程特定数据关联起来
int pthread_setspecific (pthread_key_t __key,
const void *__pointer);

//通过get函数获取线程特定数据的地址
void *pthread_getspecific (pthread_key_t __key);


一个小例子:

#define MAXSTRINGSZ 4096
static pthread_key_t key;//定义键值变量
static pthread_once_t init_done = PTHREAD_ONCE_INIT;//必须是全局或者静态,切必须用此宏初始化
pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;//定义互斥亮并且初始化
extern char **environ;

static pthread_t tid1,tid2;

static void thread_init(void)
{
pthread_key_create(&key , free);//创建键值并且关联析构函数free.函数指针
}
/*
根据name在envron里面遍历,匹配直接返回.
不匹配也返回.
*/
char *getenv2(const char *name)//不可冲入版本
{
int i , len;
char *envbuf;//直接指针做,不需要用数组缓存.

pthread_once(&init_done , thread_init);//确保键值初始化的时候不会产生竞争
pthread_mutex_lock(&env_mutex);//互斥量上锁,保护共享资源
envbuf = (char *)pthread_getspecific(key);//返回void *类型指针,所以需要进行强制转换,和自己定义变量匹配.
if(envbuf == NULL){
envbuf = (char *)malloc(MAXSTRINGSZ);//初始化envbuf指针返回void *类型指针,所以需要进行强制转换
if(envbuf == NULL){
pthread_mutex_unlock(&env_mutex);//堆内存分配失败,解锁
return(NULL);//返回空
}
pthread_setspecific(key , envbuf);//使用线程特定数据来维护每一个线程数据缓冲区副本
//用于存放各自的返回字符串.
}
len = strlen(name);
for(i = 0 ; environ[i] != NULL ; i++){
if((strncmp(name , environ[i] , len) == 0) && (environ[i][len] == '=')){
strncpy(envbuf , &environ[i][len+1] , MAXSTRINGSZ -1);//把环境变量里面的东西拷贝到备份.不直接返回环境变量系统地址,为了不让外部修改环境变量
pthread_mutex_unlock(&env_mutex);//访问共享区域结束,解锁以便其他线程可以访问.
return (envbuf);//复制4096,最后肯定对应的都是空字符.
}
}
pthread_mutex_unlock(&env_mutex);//上面for循环没有找到,在退出之前也释放锁.
return (NULL);
}
void *thr_fun1(void *arg)//指针函数,参数可以通过一个结构体传进去.
{

printf("%s\n" , getenv2("GPG_AGENT_INFO"));
pthread_exit((void *)1);
}
void *thr_fun2(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
printf("%s\n" , getenv2("GTK_MODULES"));
pthread_exit((void *)2);
}

int main(void)
{
int err;
err = pthread_create(&tid1 , NULL , thr_fun1 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 1");

err = pthread_create(&tid2 , NULL , thr_fun2 , NULL);
if(err != 0)
err_exit(err , "cant't create thread 2");
printf("%s\n" , getenv2("USER"));
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
exit(0);
}


取消选项

线程和信号

未完待续….

今天看不下去了,明天再看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: