`

Linux系统编程学习笔记(十三)线程2

阅读更多
线程2
线程1中我们介绍了线程和线程同步,本部分将学习线程控制的细节。我们将要看到线程属性、同步原语属性,线程私有数据。
1、线程限制:
Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询。和其它的限制使用目的一样,为了应用程序的在不同操作
系统的可移植性。
一些限制:
PTHREAD_DESTRUCTOR_ITERATIONS: 销毁一个线程数据最大的尝试次数,可以通过_SC_THREAD_DESTRUCTOR_ITERATIONS作为sysconf的参数查询。
PTHREAD_KEYS_MAX: 一个进程可以创建的最大key的数量。可以通过_SC_THREAD_KEYS_MAX参数查询。
PTHREAD_STACK_MIN: 线程可以使用的最小的栈空间大小。可以通过_SC_THREAD_STACK_MIN参数查询。
PTHREAD_THREADS_MAX:一个进程可以创建的最大的线程数。可以通过_SC_THREAD_THREADS_MAX参数查询
2、线程属性:
我们在调用pthread_create,线程属性参数传递了NULL值,创建的线程使用默认的线程属性。我们如果想修改默认的线程属性,可以使用pthread_attr_init
初始化pthread_attr_t结构,然后通过pthread_setxxx来修改这个结构,最后将它作为创建线程的参数,与线程关联。
我们使用完了线程属性之后可以通过pthread_attr_destroy来销毁。
#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);


pthread_attr_destroy会销毁有pthread_attr_init动态申请的内存。
pthread_attr_t结构对应用程序来说是透明的,应用程序不知道内部的结构,只能通过其他的方法来查询和设置每个属性,这和面向对象的封装性一样,有助于
提高程序的移植性。
线程定义了以下属性:
detachstate: detached线程的属性。
guardsize:线程栈后保护区的大小。
stackaddr:线程栈的最低地址。
stacksize:线程栈的大小。
我们可以通过pthread_detach来让线程在退出的时候,让操作系统回收其资源。但是如果我们不需要知道线程退出的状态,我们可以在一开始创建线程的时候就
将其设置为detach状态,这个可以通过pthread_attr_setdetachstate修改detachstate来实现,可以设置两个值:PTHREAD_CREATE_DETACHED、
PTHREAD_CREATE_JOINABLE,默认是正常的PTHREAD_CREATE_JOINABLE。
#include <pthread.h>

int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);

我们可以通过pthread_attr_getdetachstate来获得当前的detachstate,通过pthread_attr_setdetachstate来设置detachstate为:PTHREAD_CREATE_DETACHED和
PTHREAD_CREATE_JOINABLE。
例子:
创建一个detached状态的线程:
#include <pthread.h>
#include <string.h>
#include <stdio.h>

int make_detached_thread(void *(*fn)(void *), void *arg){
	int err;
	pthread_t tid;
	pthread_attr_t attr;
	
	err = pthread_attr_init(&attr);
	if(err != 0){
		return err;
	}	
	err = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	if(err == 0)
		pthread_create(&tid,&attr,fn,arg);
	pthread_attr_destory(&attr);
	return err;
}

我们忽略的pthread_attr_destory的返回值,因为它不会失败,即使失败我们也很难去进行清理,因为这是pthread_attr_destory是执行清理的接口。
支持线程stack属性是posix标准可选项,但是是XSI的必选。可以在编译的时候通过测试_POSIX_THREAD_ATTR_STACKADDR和_POSIX_THREAD_ATTR_STACKSIZE
宏来检查是否支持线程堆栈属性,也可以在运行时通过sysconf检查_SC_THREAD_ATTR_STACKADDR和_SC_THREAD_ATTR_STACKSIZE
POSIX.1定义了一些接口来操作线程堆栈属性,两个老的:pthread_attr_getstackaddr和pthread_attr_setstackaddr由于具有歧义,已经被Single UNIX标准
废弃。可以通过新的函数pthread_attr_getstack和pthread_attr_setstack来做。
#include <pthread.h>

int pthread_attr_getstack(const pthread_attr_t *restrict attr,void ** restrict stackaddr, size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t *stacksize);

这两个函数既可以操作堆栈的地址也可以操作堆栈的大小。
进程不需要担心堆栈的大小,但是使用线程时,应该小心,因为进程的虚拟地址空间被多个线程堆栈共享,如果你使用非常多的线程,你需要减少线程栈的大小,
而如果线程申请了大量的自动变量或者调用函数使用了栈帧过深(比如深层递归),那么需要增大单个线程的栈大小。
你可以使用malloc或者mmap来申请空间,然后使用pthread_attr_setstack指令另一个栈空间,stackaddr必须是栈区的最低地址,不一定是栈首,因为硬件的结构
栈地址增长可能是从低到高还是相反,这也是pthread_attr_getstackaddr具有歧义的原因。
可以使用pthread_attr_getstacksize和pthread_attr_setstacksize来获得和设置线程属性stacksize。
#include <pthread.h>

int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

如果你不想自己申请空间,你可以使用pthread_attr_setstacksize来设置栈大小。
guardsize是栈区末尾一端空间的大小,它用来防止栈溢出。默认的大小是PAGESIZE字节,我们可以把guardsize设置为0来禁止
这个特性。如果我们改变了stackaddr,系统假设我们将要自己维护栈空间并禁止掉栈防卫缓冲。
#include <pthread.h>

int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(const pthread_attr_t *attr, size_t guardsize);

guardsize应设置为页大小的整数倍,如果栈指针溢出到防卫区域,应用程序将收到错误,有可能是信号。

其他的线程属性:
线程还有其他属性,但是不是由pthread_attr_t类型表示的:
1)是否可以取消状态
2)取消类型
3)并发级别
1-2见取消选项一节
并发级别控制了用户级线程和内核级线程之间的映射关系。如果实现上二者是一对一的关系,那么改变并发级别将不起作用。如果实现上是多个用户级别的线程
映射到一个内核级别的线程,可以使用pthread_setconcurrency来向操作系统提供一个hint,让其满足期望的并发度。
#include <phtread.h>

int pthread_getconcurrency(void);
int pthread_setconcurrency(int level);

pthread_setconcurrency返回当前的并发级别,如果操作系统控制并发级别(比如没有调用过pthread_setconcurrency)。应用程序可以通过把level设置为0,来
撤销上一次调用。

3、同步属性
像线程属性一样,同步对象互斥量、读写锁、条件变量,也有他们的属性。
1)互斥量属性:
pthread_mutexattr_init初始化pthread_mutexattr_t结构,pthread_mutexattr_destroy销毁该结构。
#include <pthread.h>

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

进程共享属性和类型属性是我们感兴趣的两个互斥量属性。进程共享属性是POSIX可选的属性,可以通过测试_POSIX_THREAD_PROCESS_SHARED是否定义来检测是否支持。
也可以在运行时传_SC_THREAD_PROCESS_SHARED到sysconf来测试。Single Unix规范则要求支持此选项。
共享属性可设为两个值:PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED两个值。默认是PTHREAD_PROCESS_PRIVATE,进程及进程内线程只能访问本进程内的互斥量。
PTHREAD_PROCESS_SHARED多个进程可以共享此互斥量。
可以通过pthread_mutexattr_getshared和pthread_mutexattr_setshared来查询和设置进程共享属性:
#include <pthread.h>

int pthread_mutexattr_getpshared(const pthread_mutexattr_t * restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

类型属性:
POSIX.1定义了四种互斥量类型属性:
PTHREAD_MUTEX_NORMAL:不进行特殊的错误检查和死锁检测
PTHREAD_MUTEX_ERRORCHECK:进行错误检查
PTHREAD_MUTEX_RECURSIVE:允许同一线程在未释放锁之前多次再加锁
PTHREAD_MUTEX_DEFAULT:依赖于实现,具体映射到以上三种中的一种
可以通过pthread_mutexattr_gettype和pthread_mutexattr_settype来查询和修改type属性:

#include <pthread.h>

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

递归互斥量主要用处是:现有的单线程结构应用到多线程环境中,同时为了兼容不能改变接口。
2)读写锁属性:
和互斥量一样,读写锁也使用pthread_rwlockattr_init和pthread_rwlockattr_destroy两个方法来创建和销毁读写锁属性。
#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

读写锁唯一支持的属性有进程共享属性,这个和互斥量类似:
#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared)
int pthread_rwlockattr_setpshared(phtread_rwlockattr_t *attr, int pshared),

3)条件变量属性:
条件变量也有一对初始化和销毁的方法:
#include <pthread.h>

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);

和其他的同步原语一样,支持进程共享属性:
#include <pthread.h>

int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr *attr, int pshared);

4、可重入性:
我们已经在信号处理那块讨论了可重入函数。对于可重入来说,线程和信号处理函数类似。一个函数如果可以安全的被多个线程同时调用,
则称之为线程安全的。如果一个函数可重入,那么它是线程安全的,但使用了非可重入函数(例如malloc(3))的线程也能通过互斥等同步
机制实现线程安全。所以不能从线程安全函数是可重入函数的结果,特别是线程安全并不能保证异步信号安全。

POSIX还提供了以线程安全的方式访问FILE对象的方法,即针对FILE对象的锁同步机制:
#include <stdio.h>

int ftrylockfile(FILE *fp);
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

标准I/O例程需要他们自己的锁。但如果我们在每次一个字符的I/O操作使用锁会产生严重的性能问题。为了避免上述问题,为基于字符的标准I/O,
提供了一下四个unlock版本。
#include <stdio.h>

int getchar_unlocked(void);
int getc_unklocked(void);

int putchar_unlocked(void);
int putc_unlocked(void);

这四个函数需要在flockfile或者ftrylockfile内部使用。否则会有不可预测的结果。
线程安全的getenv例子:
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>

extern char **environ;

pthread_mutex_t env_mutex;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void thread_init(void){
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
	pthread_mutex_init(&env_mutex,&attr);
	pthread_mutex_attr_destroy(&attr);
}

int getenv_r(const char *name, char *buf, int buflen){
	int i, len, olen;
	pthread_once(&init_done,thread_init);
	len = strlen(name);
	pthread_mutex_lock(&env_mutex);
	for(i = 0; environ[i] != NULL; i++){
		if((strncmp(name,environ[i],len) == 0) && (environ[i][len] == '=')){
			olen = strlen(&environ[i][len+1];
			if(olen >= buflen){
				pthread_mutex_unlock(&env_mutex);
				return ENOSPC;
			}
			strcpy(buf,&environ[i][len+1];
			pthread_mutex_unlock(&env_mutex);
			return 0;
		}
	}
	pthread_mutex_unlock(&env_mutex);
	return ENOENT;
}

5、线程私有数据
线程私有数据是用来存储和查找与线程相关数据的一种机制。之所以叫私有数据,是因为每一个线程都访问它自己的那份数据的拷贝,不需要考虑
和其他线程同步访问数据。
使用它的原因:
1、有时候我们需要维护每个线程一份数据。虽然我们可以使用线程id作为可以的hash来做,但是还需要互斥访问和保护,防止其他线程访问该线程
的私有数据。
2、提供了将基于进程的结构适配成多线程环境的方法。比如errno
在申请和线程相关的数据之前,我们需要创建key,来和这个数据关联,我们以后会使用这个key来访问这个数据。
#include <pthread.h>

int pthread_key_create(pthread_key_t *keyp; void (*destructor)(void *));

多个线程可以使用同一个key,但是每个线程要关联不同的该线程私有的数据地址。当线程退出时(pthread_exit,return),数据的地址被设置为NULL,
参数二指定的destructor将会被调用,并将该私有数据地址作为参数传递到该函数中。
但是如果线程调用的是exit,_exit,_Exit后者abort或者其他不正常退出方法,destructor不会被调用。线程经常使用malloc申请线程私有数据,这个
destructor用于释放该数据申请的内存。
一个线程可以用多个key来关联线程的私有数据,可以使用相同的destructor,也可以不同。
如果我们想将该key和关联的私有数据断开,那么可以通过调用pthread_key_delete来完成:
#include <pthread.h>

int pthread_key_delete(pthread_key_t *key);

调用这个行数不会触发上面我们说的destructor的调用,我们需要采取其他的措施来释放。
我们需要保证key不被改变。
我们可能使用下面的错误的代码来做初始化,以保证不被多次初始化:
void destructor(void *);

pthread_key_t key;
int init_done = 0;

int threadfunc(void *arg){
	if(! init_done){
		init_done = 1;
		err = pthread_key_create(&key,destructor);
	}
	//...
}

这个由于线程条件竞争,可能被初始化多次或者看到不一致的状态。使用pthread_once可以解决这个问题:
#include <pthread.h>

pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));

参数initflag必须是非局部变量,并且被初始化为PTHREAD_ONCE_INIT。
pthread_once可以保证初始化例程initfn只被调用一次:
void destructor(void *);
pthread_key_t key;
pthread_once_t init_done = PTHREAD_ONCE_INIT;

void thread_init(void){
	err = pthread_key_create(&key,destructor);
}

int threadfunc(void *arg){
	pthread_once(&init_done,thread_init);
	//...
}

一旦key被创建,我们就可以通过调用pthread_setspecific将key和私有数据关联,可以通过调用pthread_getspecific来通过key获得
关联的数据。
#include <pthread.h>

void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);

我们前面通过改变getenv_r来改造了一个线程安全的获得环境变量的函数,如果我们不能够修改函数接口,那该如何去做?我们可以通过
使用线程私有数据来维护每个线程一个数据buffer的拷贝。
例子:
#include <limits.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

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 void thread_init(void){
	pthread_key_create(&key,free);
}

char *getenv(const char *name){
	int i, len;
	char *envbuf;

	pthread_once(&init_done, thread_init);
	pthread_mutex_lock(&env_mutex);
	envbuf = (char *)pthread_getspecific(key);
	if(envbuf == NULL){
		envbuf = malloc(ARG_MAX);
		if(envbuf == null){
			pthread_mutex_unlock(&env_mutex);
			return NULL;
		}
		pthread_seetspecific(key,envbuf);
	}
	len = strlen(name);
	for(i = 0; environ[i] != NULL; i++){
		if((strncmp(name,environ[i],len) == 0) && (environ[i][len] == '=')){
			strcpy(envbuf, &environ[i][len+1]);
			pthread_mutex_unlock(&env_mutex);
			return envbuf);
		}
	}
	pthread_mutex_unlock(&env_mutex);
	return NULL;
}

6、取消选项
两个属性没有包含在pthread_attr_t结构中:可取消状态和取消类型。取消状态可以取:PTHRAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE。
可以通过调用pthread_setcancelstate:
#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);

在一个原子操作中,将当前可取消状态设置成state,并将以前的状态设置到oldstate。
默认pthread_cancel并不使线程立即停止,线程会继续运行到取消点,一个取消点是线程检查是否被取消的地方。一个线程在调用一下函数时,
会检查是否有退出请求:
引用

accept、mq_timedsend、putpmsg、sigsuspend、aio_suspend、msgrcv、pwrite、sigtimedwait、clock_nanosleep、msgsnd、read
sigwait、close、msync、readv、sigwaitinfo、connect、nanosleep、recv、sleep 、creat、open、recvfrom、system 、fcntl2
pause、recvmsg、tcdrain 、fsync、poll、select、usleep、getmsg、pread、sem_timedwait、wait、getpmsg、pthread_cond_timedwait
sem_wait、waitid 、lockf、pthread_cond_wait、send、waitpid、mq_receive、pthread_join、sendmsg、write 、mq_send
pthread_testcancel、sendto、writev 、mq_timedreceive、putmsg、sigpause
  
如果应用程序很长时间没有没有调用上面所列的函数,那么可以通过pthread_testcancel在程序中添加自己的取消点。
#include <pthread.h>

void pthread_testcancel(void);


我们可以通过pthread_setcanceltype来改变取消的类型,而不是保持上面说的默认的行为:
#include <pthread.h>

int pthread_setcanceltype(int type, int *oldtype);

type可以被设置为:PTHREAD_CANCEL_DEFERRED或者PTHREAD_CANCEL_ASYNCHRONOUS.返回以前的type,设置到oldtype中。
异步取消PTHREAD_CANCEL_ASYNCHRONOUS不同于PTHREAD_CANCEL_DEFERRED,可以在任何时候被取消,没有必要到达一个取消点才被取消。
1
1
分享到:
评论

相关推荐

    linux系统编程笔记

    Linux系统编程笔记涉及到的内容广泛,涵盖了从基础的出错处理到进程管理,从内存管理到进程间通信,以及守护进程设计等多个层面的知识。下面详细说明各个部分的知识点: 1. 常见出错处理 - abort函数用于异常终止...

    Linux系统编程学习笔记

    ### Linux系统编程学习笔记 #### 一、IO **1.1 标准I/O (stdio)** - **fopen/fclose**: `fopen` 用于打开或创建一个文件,并返回一个指向该文件的 `FILE *` 类型的指针。`fclose` 用于关闭由 `FILE *` 指向的文件...

    Linux系统设计-Linux系统编程学习笔记

    Linux系统是一个免费使用和自由传播的类Unix操作系统,基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统,Linux是许多企业...

    linux 系统编程 尚观 linux内核驱动开发 笔记

    【Linux 系统编程与内核驱动开发笔记】 在深入探讨Linux系统编程和内核驱动开发之前,我们首先要理解Linux操作系统的基本概念。Linux是一种自由、开放源码的类Unix操作系统,广泛应用于服务器、桌面环境以及各种...

    Linux系统设计-linux系统网络编程学习笔记

    Linux系统是一个免费使用和自由传播的类Unix操作系统,基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统,Linux是许多企业...

    linux编程学习笔记PDF资料下载.txt

    根据提供的文件信息,我们可以推断出这是一份关于Linux编程学习笔记的PDF资料。下面将对这份资料可能涉及的关键知识点进行详细的阐述。 ### Linux编程基础知识 #### 1. Linux操作系统概述 - **定义与特点**:Linux...

    linux系统编程及网络编程笔记

    总的来说,"Linux系统编程及网络编程笔记"会详细解析上述各个方面,包括理论知识、实际示例和最佳实践,旨在帮助学习者深入理解Linux环境下的系统编程和网络编程,为开发高效、安全的系统级应用程序和网络服务打下...

    Linux多线程编程_linux多线程_Linux多线程;应用笔记_columnc9g_

    在Linux系统中,多线程编程是一种常见的提升程序并发性能的技术。它允许一个进程内同时执行多个线程,每个线程都有自己的执行上下文,但共享同一份内存空间。这使得线程间通信更为便捷,同时也增加了编程的复杂性。 ...

    Linux系统设计-linux 编程环境学习笔记,含 linux 基本命令,linux 操作系统,linux 下 C++ 编程等

    Linux系统是一个免费使用和自由传播的类Unix操作系统,基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统,Linux是许多企业...

    线程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

    线程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

    线程同步-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

    线程同步-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

    Linux系统设计-Linux系统编程笔记

    Linux系统是一个免费使用和自由传播的类Unix操作系统,基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统,Linux是许多企业...

    多线程学习笔记

    多线程学习笔记 iOS开发中,多线程是一种常见的技术手段,用于优化应用程序的性能,提升用户体验。多线程的核心是让程序能够并发地执行多个任务,合理地利用设备的计算能力,尤其是在拥有多个核心的处理器上。 ...

    韩顺平linux学习笔记

    学习笔记分为多个阶段,从基础的 Linux 平台开发到高级的 Unix 环境编程,并涉及到 Linux 应用系统开发和嵌入式开发等方面。 Linux 基础知识 Linux 是一个开源、免费的操作系统,其稳定性、安全性、处理多并发已经...

    Linux 进程 线程学习笔记

    在深入探讨Linux下C语言编程中进程和线程的创建之前,我们先来理解一下进程与线程的基本概念。 - **进程**:是操作系统进行资源分配和调度的基本单位,每个进程都有独立的地址空间、内存和系统资源。在Linux环境下...

    Posix多线程编程学习笔记1~6

    Posix多线程编程学习笔记1~6,包括线程基础,线程属性,信号灯,条件变量,互斥变量,共享内存六部分内容。很有助于linux下多线程的开发。文档主要包含介绍相应的pthread接口函数,并举了部分相应的例子。

    linux的进程线程编程(高清书签版)

    综上所述,Linux进程线程编程的学习笔记整理涵盖了进程的基本概念、进程的创建与管理、进程类型、进程状态转换、线程编程、进程间通信和信号处理等多个方面。掌握这些知识点对于深入理解Linux操作系统运行机制、编写...

Global site tag (gtag.js) - Google Analytics