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

linux多线程pthread

2014-07-14 17:20 204 查看
http://blog.chinaunix.net/uid-22163090-id-401346.html

1. 所谓线程就是“一个进程内部的一个控制序列”。也就是一个进程内部的并行的基础!

2. Linux进程可以看成只有一个控制线程:

一个进程在同一时刻只做一件事情。有了多个控制线程以后,

在程序设计时可以把进程设计成在同一时刻能够做不止一件事,

每个线程处理各只独立的任务。即所谓并行!

3. 线程的优点:

(1) 通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。

(2) 多个线程可以自动共享相同的存储地址空间和文件描述符。

(3) 有些问题可以通过将其分解从而改善整个程序的吞吐量。

(4) 交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中

处理用户输入输出的部分与其它部分分开。

4. 线程的缺点:

线程也有不足之处。编写多线程程序需要更全面更深入的思考。

在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的

变量而造成不良影响的可能性是很大的。调试一个多线程程序也

比调试一个单线程程序困难得多。

5. 线程标识:

我们已经知道进程有进程ID就是pid_t,那么线程也是有自己的ID的pthread_t数据类型!

注意:实现的时候可以用一个结构来代表pthread_t数据类型,所以可以移植的操作系统

不能把它作为整数处理。因此必须使用函数来对来对两个线程ID进行比较。

>>>>>:

关于比较线程ID函数:

#include <pthread.h>

int pthread_equal(pthread_t tid1,pthread_t tid2 );

注意:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,

所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,

而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。

所以不能直接使用==判读,而应该使用pthread_equal来判断。

返回:非0值->相等;0->不等价

>>>>>:

获取自身线程的id:

#include <pthread.h>

pthread_t pthread_self(void);

返回:调用线程的线程id

注意:在使用pthread_create(pthread_t *thread_id,NULL,void* (*fun) (void *),void * args);

虽然第一个参数中已经保存了线程ID,但是,前提是主线程首先执行时,才能实现的,

而如果不是,那么thread指向一个未初始化的变量。那么子线程想使用时,应该使用pthread_self();

6. 线程的创建:

#include <pthread.h>

int pthread_create(pthread_t *restrict tidp,const pthread _attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg);

参数:

   第一个参数为指向线程标识符的指针。

   第二个参数用来设置线程属性。

   第三个参数是线程运行函数的起始地址。

   最后一个参数是运行函数的参数

返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的

线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如

果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个

结构的地址作为arg的参数传入。

注意:restrict修饰,

pthread_join

运行:

在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库

if不加上就会报错:undefined reference to `pthread_create'。

7. 线程的终止与等待:

线程是依进程而存在的,当进程终止时,线程也就终止了。当然也有在不终止整个进程的情况下停止它的控制流。

<1>. 线程从启动例程中返回,返回值是线程的退出码。

<2>. 线程可以被同一进程中的其他线程取消。

<3>. 线程退出调用pthread_exit.

#include <pthread.h>

void pthread_exit( void * rval_ptr );

rval_ptr 是一个无类型指针,与传给启动例程的单个参数类似。

进程中的其他线程可以调用pthread_join函数访问到这个指针。

pthread_join:获得进程的终止状态。

int pthread_join( pthread_t thread, void ** rval_ptr );

若成功返回0,否则返回错误编号。

调用pthread_join进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。

如果线程只是从它的启动历程返回,rval_ptr将包含返回码。

8. pthread_detach:使线程进入分离状态

int pthread_detach( pthread_t tid );

若成功则返回0,否则返回错误编号。

注意:默认情况下是pthread_join等待线程的结束,并可以获得结束时的状态!

如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。

当线程被分离时,并不能用pthread_join函数等待它的终止状态。

9. pthread_cancel:取消同一进程中的其他线程(注意是取消其他线程)

int pthread_cancel(pthread_t tid);

若成功返回0,否则返回错误编号。

注意:pthread_cancel并不等待线程终止,它仅仅提出请求。是否真的执行还看目标线程的

state和type的设置了!

10. pthread_cleanup_push 和 pthread_cleanup_pop:线程清理处理程序

void pthread_cleanup_push( void ( * rtn ) ( void * ),void *arg );

void pthread_cleanup_pop( int exe );

参数:

rtn: 处理程序入口地址

arg: 传递给处理函数的参数

线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序,线程可以建立多个清理处理程序。

注意:此处是使用栈保存的,所以是先进后处理原则!

如果线程是通过从启动例程中返回而终止的,它的处理程序就不会调用。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

ATTENTION:

pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop

之前异常退出(return是正常退出,其他是异常),那么系统就会执行这个回调函数(回调函

数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常退出,

pthread_cleanup_pop就把对应的回调函数取消了,

所以请注意:

只有在“清理函数”的设置和取消之间有异常的退出,才会调用我们设置的“清理函数”。

否则是不会输出的!

一般的触发条件是:在之间有pthread_exit(非正常退出);或者有取消点时候!

关于“取消点” ( cancellation point ):

例如执行下面代码:

printf(" sleep\n");

sleep(10);

printf(" wake \n");

在sleep函数中,线程睡眠,结果收到cancel信号,这时候线程从sleep中醒来,但是线程不会立刻退出。

>>>>>:

函数:pthread_testcancel():

描述:函数在运行的线程中创建一个取消点,如果cancellation无效则此函数不起作用。

pthread的建议是:如果一个函数是阻塞的,那么你必须在这个函数前后建立 “ 取消点 ”, 比如:

printf(" sleep\n");

pthread_testcancel();

sleep(10);

pthread_testcancel();

printf(" wake \n");

在执行到pthread_testcancel的位置时,线程才可能响应cancel退出进程。

对于一些函数来说本身就是有cancellation point 的,那么可以不管,但是大部分还是没有的,

所以要使用pthread_testcancel来设置一个取消点,那么也并不是对于所有的函数都是有效的,

对于有延时的函数才是有效的,更清楚的说是有时间让pthread_cancel响应才是OK的!

附加:

POSIX中的函数cancellation点的:

pthread_join

pthread_cond_wait

thread_cond_timewait

pthread_testcancel

sem_wait

sigwait 都是cancellation点.

下面的这些系统函数也是cancellation点:

accept

fcntl

open

read

write

lseek

close

send

sendmsg

sendto

connect

recv

recvfrom

recvmsg

system

tcdrain

fsync

msync

pause

wait

waitpid

nanosleep

其它的一些函数如果调用了上面的函数, 那么, 它们也是cancellation点.

int pthread_setcancelstate (int STATE, int *OLDSTATE);

用于允许或禁止处理cancellation,

STATE可以是:PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DISABLE

int pthread_setcanceltype (int TYPE, int *OLDTYPE);

设置如何处理cancellation, 异步的还是推迟的.

TYPE可以是:PTHREAD_CANCEL_ASYNCHRONOUS, PTHREAD_CANCEL_DEFERRED

>>>>

摘录:http://blogt.chinaunix.net/space.php?uid=23381466&do=blog&id=58787

什么是取消点(cancelation point)?

资料中说,根据POSIX标准,pthread_join()、pthread_testcancel()、 pthread_cond_wait()、

pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及 read()、write()等会引起阻塞

的系统调用都是Cancelation-point。而其他pthread函数都不会引起 Cancelation动作。但

是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函

数都不是 Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置

EINTR错误码,因此可以在需要作为 Cancelation-point的系统调用前后调用pthread_testcancel(),

从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();

retcode = read(fd, buffer, length);

pthread_testcancel();

我发现,对于C库函数来说,几乎可以使线程挂起的函数都会响应CANCEL信号,终止线程,

包括sleep、delay等延时函数。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

11.

#include <pthread.h>

int pthread_setcancelstate( int state, int* oldstate );

描述:

pthread_setcancelstate() f函数设置线程取消状态为state,并且返回前一个取消点状态oldstate。

取消点有如下状态值:

PTHREAD_CANCEL_DISABLE:取消请求保持等待,默认值。

PTHREAD_CANCEL_ENABLE:取消请求依据取消类型执行;参考pthread_setcanceltype()。

参数:

state: 新取消状态。

oldstate: 指向本函数所存储的原取消状态的指针。

///////////////////////////////////////////////////////////////////////////

#include <pthread.h>

int pthread_setcanceltype( int type, int* oldtype );

描述:

pthread_setcanceltype()函数设置运行线程的取消类型为type,并且返回原取消类型与oldtype。

取消类型值:

   PTHREAD_CANCEL_ASYNCHRONOUS:如果取消有效,新的或者是等待的取消请求会立即执行。

   PTHREAD_CANCEL_DEFERRED:如果取消有效,在遇到下一个取消点之前,取消请求会保持等待,默认值。

   注意if没有取消点,那么陷入死锁!!!

注意:标注POSIX和C库的调用不是异步取消安全地。

参数:

type: 新取消类型

oldtype: 指向该函数所存储的原取消类型的指针。

12. posix变量一次初始化问题:

>>>>>:

如果我们需要对一个posix变量静态的初始化,可使用的方法是用一个互斥量对该变量的初始话进行控制。

但有时候我们需要对该变量进行动态初始化,pthread_once就会方便的多。

pthread_once_t once_control = PTHREAD_ONCE_INIT;

int pthread_once( pthread_once_t *once_control, void( * init_routine ) ( void ) );

参数:once_control 控制变量

init_routine 初始化函数

若成功返回0,若失败返回错误编号。

pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。

pthread_once 工作原理:

pthread_once函数首先检查控制变量,判断是否已经完成初始化,如果完成

就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。

如果在一个线程初始时,另外的线程调用pthread_once,则调用线程等待,直到

那个现成完成初始话返回。

13. 线程的私有数据:

在进程内的所有线程共享相同的地址空间,任何声明为静态或外部的变量,或在进程堆声明的变量,

都可以被进程所有的线程读写。那怎样才能使线程序拥有自己的私有数据呢。posix提供了一种方法,

创建 " 线程键 "。

#include <pthread.h>

int pthread_key_create(pthread_key *key,void(*destructor)(void *));

参数:

key 私有数据键

destructor 清理函数参数

if destructor 不为空,那么系统将调用这个函数来释放绑定在这个键上的内存块。

这个函数常和函数pthread_once一起使用,为了让这个键只被创建一次。

14. 关于: " 线程存储 "

1.基础线程创建:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

void * print_id( void * arg ) //!> 这是线程的入口函数

{

printf("The Current process is: %d \n", getpid()); //!> 当前进程ID

printf( "The Current thread id : %d \n", (unsigned)pthread_self() ); //!> 注意此处输出的子线程的ID

}

int main( )

{

pthread_t t;

int t_id;

t_id = pthread_create( &t, NULL, print_id, NULL ); //!> 简单的创建线程

if( t_id != 0 ) //!> 注意创建成功返回0

{

printf("\nCreate thread error...\n");

exit( EXIT_FAILURE );

}

sleep( 1 );

printf("\nThe Current process is: %d \n", getpid()); //!> 当前进程ID

printf( "The Main thread id : %d \n", (unsigned)pthread_self() ); //!> 注意输出的MAIN线程的ID

return 0;

}

2.测试线程的创建和退出

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <stdlib.h>

void * entrance_1( void * arg ) //!> 第一个创建的线程的入口函数

{

printf( " thread 1 id == %d , run now ... \n", ( unsigned )pthread_self() );

sleep( 3 );

return ( ( void * ) 1 );

}

void * entrance_2( void * arg ) //!> 第二个创建的线程的入口函数

{

printf( " thread 2 id == %d , run now ... \n", ( unsigned )pthread_self() );

sleep( 3 );

return ( ( void * ) 2 );

}

int main( )

{

pthread_t t1 = -1; //!> 最好是初始化:因为下面的pthread_join是需要判断是否成功在输出的

pthread_t t2 = -1;

int tid1;

int tid2;

void * ret;

tid1 = pthread_create( &t1, NULL, entrance_1, NULL ); //!> 简单的创建线程

tid2 = pthread_create( &t2, NULL, entrance_2, NULL );

if( tid1 != 0 || tid2 != 0 ) //!> 创建线程失败

{

printf( "Create thread error...\n" );

exit( EXIT_FAILURE );

}

if( t1 != -1 ) //!> 也就是线程还没有结束

{

if ( pthread_join( t1, &ret ) == 0 ) //!> join success

{

printf( " thread 1 get the return of pthread_join == %d \n", 2 );/ )

{

pthread_mutex_init( &mutex, NULL ); //!> 初始化为默认的互斥锁

printf("主函数:创建2个子线程...\n");

create_two_thread(); //!> 创建2个线程

printf("主函数:等待线程完成任务...\n");

wait_two_thread(); //!> 等待线程完成任务

//!> 线程任务完成才可以执行下面代码

printf("线程任务完成...\n");

printf("Num == %d \n\n", num);

return 0;

}

4.双线程处理:冒泡排序算法

// 双线程处理冒泡排序(多线程也一样)

// 实现从“小”--->“大”排序

#include <stdio.h>

#include <string.h>

#include <pthread.h>

#include <stdlib.h>

int g_arr[] = { 10, 23, 12, 34, 5, 29, 90, 9, 78, 44 }; //!> 全局的要排序的数组

pthread_t thread[2]; //!> 两个线程

pthread_mutex_t mutex; //!> 互斥锁

int g_i = 0; //!> 全局的剩余排列次数

//!> 打印数组

void print_array()

{

int i;

for( i = 0; i < 10; i++ )

{

printf( " %d ", g_arr[i] );

}

printf("\n");

}

//!> 交换元素

void swap_elem( int * a, int * b )

{

int temp;

temp = *a;

*a = *b;

*b = temp;

}

//!> 线程1入口函数

void * entrance_1( void * arg )

{

int j;

for( g_i = 0; g_i < 10; g_i++ ) //!> 外层循环

{

pthread_mutex_lock( &mutex ); //!> 加锁

printf( "线程1后台执行排序...\n" );

for( j = 0; j < ( 10 - g_i - 1 ); j++ ) //!> 内层循环

{

if( g_arr[j] > g_arr[j+1] )

{

swap_elem( &g_arr[j], &g_arr[j+1] );

}

}

pthread_mutex_unlock( &mutex ); //!> 解锁

sleep( 1 );

}

}

//!> 线程2入口函数

void * entrance_2( void * arg )

{

int j;

for( g_i = 0; g_i < 10; g_i++ ) //!> 外层循环

{

pthread_mutex_lock( &mutex ); //!> 加锁

printf( "线程2后台执行排序...\n" );

for( j = 0; j < ( 10 - g_i - 1 ); j++ ) //!> 内层循环

{

if( g_arr[j] > g_arr[j+1] )

{

swap_elem( &g_arr[j], &g_arr[j+1] );

}

}

pthread_mutex_unlock( &mutex ); //!> 解锁

sleep( 2 );

}

}

//!> 创建2个线程

void create_two_thread()

{

memset( &thread, 0, sizeof( thread ) ); //!> 初始化为0(作为下面的判断进程是否创建OK依据)

if( ( pthread_create( &thread[0], NULL, entrance_1, NULL ) ) == 0 )

{

printf("线程1创建OK ...\n");

}

else

{

printf("线程1创建Error ...\n");

exit( EXIT_FAILURE );

}

if( ( pthread_create( &thread[1], NULL, entrance_2, NULL ) ) == 0 )

{

printf("线程2创建OK ...\n");

}

else

{

printf("线程2创建Error ...\n");

exit( EXIT_FAILURE );

}

}

//!> 线程执行与等待

void do_and_wait()

{

if( thread[0] != 0 )//!> 由于在create_two_thread中初始化=0,if床架ok,那么不可能还是0

{

pthread_join( thread[0], NULL ); //!> 等待线程1结束,不结束不执行下面代码

printf("线程1执行结束退出...\n");

}

else

{

printf("线程1创建Error...\n");

exit( EXIT_FAILURE );

}

if( thread[1] != 0 )

{

pthread_join( thread[1], NULL ); //!> 等待线程1结束,不结束不执行下面代码

printf("线程2执行结束退出...\n");

}

else

{

printf("线程2创建Error...\n");

exit( EXIT_FAILURE );

}

}

int main( )

{

printf("主函数:下面创建2个线程共同处理冒泡排序...\n");

pthread_mutex_init( &mutex, NULL );

print_array(); //!> 打印排序前的结果

create_two_thread(); //!> 创建线程

do_and_wait(); //!> 执行线程and等待

printf( "排序完成:\n" );

print_array(); //!> 打印排序后的结果

return 0;

}

5.线程清理处理程序

// 线程清理处理程序TEST

#include <stdlib.h>

#include <stdio.h>

#include <pthread.h>

void clean(void *arg)

{

printf("清理: %s \n", (char *)arg);

}

void * entrance( void * arg )

{

printf("线程开始...\n");

pthread_cleanup_push( clean, "线程处理程序1" );

pthread_cleanup_push( clean, "线程处理程序2" );

printf("pthread clean 完成...\n");

sleep(3);

pthread_exit((void *)0); //!> 我们知道:清理函数只有在异常退出时候才会做一些清理工作

//!> 所以此处的退出是异常退出来测试的!

pthread_cleanup_pop(0);

pthread_cleanup_pop(0);

}

int main( )

{

pthread_t tid;

void * ret = NULL;

if( ( pthread_create( &tid, NULL, entrance, (void *)1 ) ) != 0 )

{

printf("创建线程失败...\n");

exit( EXIT_FAILURE );

}

pthread_join( tid, &ret );

if( ret ) //!> 注意此处相当于是抛出异常

{ //!> 避免子线程的异常退出造成的空指针情况

printf( "结束:code == %d\n", *( ( int * ) ret) );

}

return 0;

}

/////////////////////////////////////////////////////////////

// DEMO——2

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

void clean( void * arg )

{

printf("清理函数执行...\n");

}

void * entrance( void * arg )

{

int old_type, old_state;

int i = 0;

pthread_cleanup_push( clean, NULL ); //!> 设置清理函数

printf("下面设置对本线程的“取消”无效\n");

pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &old_state );

//!> 设置对本线程的“取消”无效

pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&old_type); //>>>>>>>>>>> 目标句1

while( 1 )

{

++i;

printf("子线程runing...\n");

sleep( 2 );

if( 5 == i )

{

printf("下面取消设置对本线程的“取消”无效\n");

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);

}

}

pthread_cleanup_pop( 0 );

}

int main( int argc, char ** argv )

{

pthread_t tid;

int res;

void * ret;

pthread_create( &tid, NULL, entrance, NULL );

sleep( 2 );

printf("请求子线程退出...\n");

pthread_cancel( tid ); //!> 请求子线程退出

res = pthread_join( tid, &ret ); //!> 等待子线程退出

if( ret != PTHREAD_CANCELED ) //!> 非安全退出

{

printf("pthread_join 失败...\n");

exit( EXIT_FAILURE );

}

else

{

printf("Success..");

}

exit( EXIT_SUCCESS );

}

分析:

没有加上“目标句”的结果是:

下面设置对本线程的“取消”无效

子线程runing...

请求子线程退出...

子线程runing...

子线程runing...

子线程runing...

子线程runing...

下面取消设置对本线程的“取消”无效

子线程runing... //!>
比下面多的

清理函数执行...

Success.. //!>
与下面不一样的

加上后:

下面设置对本线程的“取消”无效

子线程runing...

请求子线程退出...

子线程runing...

子线程runing...

子线程runing...

子线程runing...

下面取消设置对本线程的“取消”无效

清理函数执行...

pthread_join 失败... //!>
与上面不一样的

这句的作用是将取消类型设置为PTHREAD_CANCEL_ASYNCHRONOUS,即取消请求会被立即响应

那么就不会再次进入等待下一个“取消点”再进行取消!!!

注意:if在while中没有sleep,那么程序会无限run,我们知道sleep是相当于是释放一下线程,那么此时的主线程中的cancel信号又被接收到,那么if本函数可以响应了,那么就cancle了,if将sleep去掉,那么死循环!再次解释:所谓pthread_cancel仅仅是请求某个线程退出,那么究竟是不是退出还要看state和type的设置!

6. pthread_once 工作原理code

// pthread_once 函数使用

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

pthread_once_t once = PTHREAD_ONCE_INIT; //!> once宏赋值

//!> 初始化执行函数

void once_init( void )

{

printf("初始化成功! 我的ID == %d\n", (unsigned)pthread_self());

}

//!> 线程入口函数

void * entrance( void * arg )

{

printf("子线程:ID == %d \n", (unsigned)pthread_self());

//!> once = PTHREAD_ONCE_INIT; //!> 测试使用(下面的要求)

pthread_once( &once, once_init ); //!> 此处也有初始化

}

//!> main函数

int main( int argc, char * argv[] )

{

pthread_t pid;

pthread_create( &pid, NULL, entrance, NULL );

printf("主函数ID == %d \n", (unsigned)pthread_self());

//!> pthread_join( pid, NULL ); //!> 分析点

pthread_once( &once, once_init ); //!> 调用一次初始化函数

pthread_join( pid, NULL );

return 0;

}

if pthread_join是在主函数初始化后面,那么就是主函数初始化的

结果是: 主函数ID == 441960192

初始化成功! 我的ID == 441960192

子线程:ID == 433944320

显而易见是主函数初始化的!

if pthread_join是在之前,那么就是要等待子函数执行ok后才执行自己的下面代码

但是此时已经初始化ok了,所以不在初始化!

结果是:主函数ID == 210818816

子线程:ID == 202802944

初始化成功! 我的ID == 202802944

显然是子函数执行的初始化!

本质: 其实就是操作once变量而已,与互斥变量的本质是一样的!!!

我们可以这样测试在entrance中加入once = PTHREAD_ONCE_INIT;

结果是:主函数ID == 1590228736

初始化成功! 我的ID == 1590228736

子线程:ID == 1582212864

初始化成功! 我的ID == 1582212864

感兴趣的可以使用pthread_mutex_t 的互斥变量处理,效果一样!

还有最最简单的就是bool值处理!此处不建议!

7.pthread_key_create线程键 与 线程存储

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

pthread_once_t once = PTHREAD_ONCE_INIT;

pthread_key_t key; //!> 键值

int g_val = 10; //!> 传说中的独享值,呵呵

void once_init_key()

{

if( pthread_key_create( &key, NULL ) == 0 ) //!> 创建线程键值

{ //!>

printf("创建线程键OK ...\n");

}

}

void * entrance( void * arg )

{

int * val;

printf("子线程:ID == %d \n", (unsigned)pthread_self());

pthread_setspecific( key, &g_val ); //!> 将 g_val 作为一个每个进程的独享值

val = ( int * )pthread_getspecific( key ); //!> 取出那个值

//!>
此后对于这些量都有自己的处理方式,

//!>
名称相同但是内容不同!!!

//!>
对于文件的处理是最好的!!!

printf("ID == %d, Value == %d\n", (unsigned)pthread_self(), *val);

}

int main( )

{

pthread_t tid, tid2;

void * ret1;

void * ret2;

if( pthread_create( &tid, NULL, entrance, NULL ) != 0 ) //!> 线程1

{

printf("创建线程1失败...\n");

exit( EXIT_FAILURE );

}

if( pthread_create( &tid2, NULL, entrance, NULL ) != 0 ) //!> 线程2

{

printf("创建线程2失败...\n");

exit( EXIT_FAILURE );

}

printf("主函数:ID == %d \n", (unsigned)pthread_self());

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

pthread_once( &once, once_init_key ); //!> 创建一个键值

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

printf("下面等待子线程执行ok... \n");

pthread_join( tid, &ret1 ); //!> 等待线程( 必不可少 )

pthread_join( tid2, &ret2 );

return 0;

}

结果:

主函数:ID == 1588877056

创建线程键OK ...

子线程:ID == 1580861184

ID == 1580861184, Value == 10

下面等待子线程执行ok...

子线程:ID == 1572468480

ID == 1572468480, Value == 10

1. 线程属性:

使用pthread_attr_t类型表示,我们需要对此结构体进行初始化,

初始化后使用,使用后还要进行去除初始化!

pthread_attr_init:初始化

pthread_attr_destory:去除初始化

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);

int pthread_attr_destroy(pthread_attr_t *attr);

若成功返回0,若失败返回-1。

pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现

支持的线程所有属性的默认值。

如果pthread_attr_init实现时为属性对象分配了动态内存空间,

pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经

pthread_attr_destroy去除初始化之后的pthread_attr_t结构被

pthread_create函数调用,将会导致其返回错误。

线程属性结构如下:

typedef struct

{

int detachstate; 线程的分离状态

int schedpolicy; 线程调度策略

struct sched_param schedparam; 线程的调度参数

int inheritsched; 线程的继承性

int scope; 线程的作用域

size_t guardsize;
线程栈末尾的警戒缓冲区大小

int stackaddr_set;

void * stackaddr; 线程栈的位置

size_t stacksize; 线程栈的大小

}pthread_attr_t;

下面主要讨论此结构体!!!

2. 分离状态:

线程的分离状态决定一个线程以什么样的方式来终止自己。

我们已经在前面已经知道,在默认情况下线程是非分离状态的,这种情况

下,原有的线程等待创建的线程结束。只有当pthread_join() 函数返回

时,创建的线程才算终止,才能释放自己占用的系统资源。

分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,

马上释放系统资源。

通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,

主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是if有一

些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我

们就无须等待管理,只要线程自己结束了,自己释放src就可以咯!这样更

方便!

#include <pthread.h>

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

int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);

参数:attr:线程属性变量

detachstate:分离状态属性

若成功返回0,若失败返回-1。

设置的时候可以有两种选择:

<1>.detachstate参数为:PTHREAD_CREATE_DETACHED 分离状态启动

<2>.detachstate参数为:PTHREAD_CREATE_JOINABLE 正常启动线程

3. 线程的继承性:

函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设

置和得到线程的继承性!

#include <pthread.h>

int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);

int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);

参数:

attr 线程属性变量

inheritsched 线程的继承性

若成功返回0,若失败返回-1。

请注意:

继承性决定调度的参数是从创建的进程中继承还是使用在

schedpolicy和schedparam属性中显式设置的调度信息。

线程没有默认的继承值设置,所以如果关心线程的调度策略和参数,

只能手动设置!

可设置参数:

PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数!

PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于

schedpolicy和schedparam属性中显式

设置的调度信息!

>>>>>: 下面补充线程调度策略和调度参数:

<1>.调度策略:

函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用

来设置和得到线程的调度策略。

int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)

int pthread_attr_setschedpolicy(pthread_attr_*, int policy)

参数:

attr 线程属性变量

policy 调度策略

若成功返回0,若失败返回-1。

所谓调度策略也就是我们之前在OS中所学过的那些调度算法:

SCHED_FIFO :先进先出

SCHED_RR :轮转法

SCHED_OTHER :其他方法

SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR

支持优先级的使用,他们分别为1和99,数值越大优先级越高.

注意:

> 此处的SCHED_FIFO是允许被高优先级抢占的!

> 也就是有高优先级的必须先运行

> SCHED_RR是设置一个时间片

> 当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量

上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤

醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先

织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量

被解锁时,高优先级线程将总是被首先解除阻塞。

<2>.调度参数:

函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别

用来设置和得到线程的调度参数。

int pthread_attr_getschedparam(const pthread_attr_t *,struct

sched_param *);

int pthread_attr_setschedparam(pthread_attr_t *,const struct

sched_param *);

参数:

attr 线程变量属性

param sched_parm 结构体

若成功返回0,若失败返回-1。

/usr/include /bits/sched.h

struct sched_param

{

int sched_priority; //!> 参数的本质就是优先级

};

注意:大的权值对应高的优先级!

系统支持的最大和最小的优先级值可以用函数:

sched_get_priority_max和sched_get_priority_min得到!

#include <pthread.h>

int sched_get_priority_max( int policy );

int sched_get_priority_min( int policy );

参数:max_: 系统支持的优先级的最小值

min_ : 系统支持的优先级的最大值

使用:max_ = sched_get_priority_max( policy );

min_ = sched_get_priority_min( policy );

注意参数是policy调用策略,也就是说对于不同的策略的值是不

一样的!

附录:来自
http://www.yuanma.org/data/2006/0823/article_1392.htm
policy = SCHED_OTHER

max_priority = 0

min_priority = 0

Show SCHED_FIFO of priority

max_priority = 99

min_priority = 1

Show SCHED_RR of priority

max_priority = 99

min_priority = 1

Show priority of current thread

priority = 0

3. 线程的作用域:

函数pthread_attr_setscope和pthread_attr_getscope分别

用来设置和得到线程的作用域。

#include <pthread.h>

int pthread_attr_getscope( const pthread_attr_t * attr, int * scope );

int pthread_attr_setscope( pthread_attr_t*, int scope );

参数:

attr 线程属性变量

scope 线程的作用域

若成功返回0,若失败返回-1。

作用域控制线程是否在进程内或在系统级上竞争资源,可能的值是

PTHREAD_SCOPE_PROCESS(进程内竞争资源)

PTHREAD_SCOPE_SYSTEM (系统级竞争资源)。

4. 线程堆栈的大小

函数pthread_attr_setstackaddr和pthread_attr_getstackaddr分别用来设置和得

到线程堆栈的位置。

int pthread_attr_getstacksize(const pthread_attr_t *,size_t * stacksize);

int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);

参数:attr 线程属性变量

stacksize 堆栈大小

若成功返回0,若失败返回-1。

5. 线程堆栈的地址

#include <pthread.h>

int pthread_attr_getstackaddr(const pthread_attr_t *attr,void **stackaddf);

int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);

参数:attr 线程属性变量

stackaddr 堆栈地址

若成功返回0,若失败返回-1。

注意:pthread_attr_getstackaddr已经过期,现在使用的是:pthread_attr_getstack

6. 警戒缓冲区

函数pthread_attr_getguardsize和pthread_attr_setguardsize分别用来设置和得

到线程栈末尾的警戒缓冲区大小。

#include <pthread.h>

int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict

guardsize);

int pthread_attr_setguardsize(pthread_attr_t *attr ,size_t *guardsize);

若成功返回0,若失败返回-1。

值得注意:

线程属性guardsize控制着线程栈末尾之后以避免栈溢出的扩展内存

大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线

程属性设为0,从而不允许属性的这种特征行为发生:在这种情况

下不会提供警戒缓存区。同样地,如果对线程属性stackaddr作了

修改,系统就会认为我们会自己管理栈,并使警戒栈缓冲区机制无

效,等同于把guardsize线程属性设为0。

linux thread与fork的对比

进程原语线程原语 描述
forkpthread_create 创建新的控制流
exitpthread_exit从现有的控制流退出
waitpidpthread_join从控制流中得到退出状态
atexitpthread_clean_push注册在退出控制流时执行的函数
getpidpthread_self获得控制流ID
abortpthread_cancel请求控制流的非正常退出
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: