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的对比
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的对比
进程原语 | 线程原语 | 描述 |
fork | pthread_create | 创建新的控制流 |
exit | pthread_exit | 从现有的控制流退出 |
waitpid | pthread_join | 从控制流中得到退出状态 |
atexit | pthread_clean_push | 注册在退出控制流时执行的函数 |
getpid | pthread_self | 获得控制流ID |
abort | pthread_cancel | 请求控制流的非正常退出 |
相关文章推荐
- linux多线程---pthread_cancel
- Linux多线程编程详细解析----条件变量 pthread_cond_t
- Linux多线程编程 - sleep 和 pthread_cond_timedwait
- linux多线程的总结(pthread用法) - cy163 - 博客园
- Linux多线程实例练习 - pthread_exit() 与 pthread_join()
- Linux多线程编程 - sleep 和 pthread_cond_timedwait
- Linux多线程实例练习 - pthread_cancel()
- Linux多线程Pthread学习小结
- Linux多线程pthread_key_create
- linux多线程 undefined reference to 'pthread_create'
- Linux多线程 PTHREAD_EXPLICIT_SCHED
- Linux多线程 编程详细解析----条件变量 pthread_cond_t
- Linux多线程编程 - sleep 和 pthread_cond_timedwait
- linux多线程之pthread_create
- Linux多线程编程详细解析----条件变量 pthread_cond_t
- Linux多线程编程详细解析----条件变量 pthread_cond_t
- linux多线程 pthread_cond_wait() 函数
- Linux多线程Pthread学习小结
- linux多线程pthread
- linux多线程---pthread_cancel