多线程的使用

线程

定义
英文:Thread
每个正在系统上运行的 程序都是一个 进程。每个 进程包含一到多个线程。 进程也可能是整个 程序或者是部分程序的动态执行。线程是一组 指令的集合,或者是 程序的特殊段,它可以在程序里独立执行。也可以把它理解为 代码运行的上下文。所以线程基本上是轻量级的 进程,它负责在单个 程序里执行多 任务。通常由 操作系统负责多个线程的调度和执行。
线程是 程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
线程和 进程的区别在于,子进程和 父进程有不同的 代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行 堆栈程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体 情况而定. 线程的运行中需要使用计算机的 内存资源和CPU。

 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
  使用多线程的理由之二是 线程间方便的通信机制 。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然, 由于同一进程下的线程之间共享数据空间 ,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
  1) 提高应用程序响应。 这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2) 使多CPU系统更加有效 。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
看一个简单的线程的例子:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>


void* thread(void *arg)
{
	while(1)
	{
		printf("%s\n",(char*)arg);
		sleep(2);
	}
	return NULL;
}


int main(void)
{
	pthread_t id;//线程标识符	
	int i,ret;
	ret = pthread_create(&id,NULL,thread,"taotao 2b");
	
	if(ret != 0)//执行不成功,成功的话函数会返回0 
	{
		printf("Create pthread error");
		exit(-1);
	}
	for(i = 0;i < 3; i++)
	{
		printf("this is the main process\n");
		sleep(1);
	}
		
	
	return 0;
}	
	
	


编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。
 gcc pthread.c -lpthread -o example
./example 
运行结果:

可以看到主函数运行结束后,线程也自动退出不再打印了。
用到的函数:

函数简介

pthread_create是UNIX环境创建线程函数

编辑本段头文件

#include<pthread.h>

编辑本段函数声明

int pthread_create(pthread_t*restrict tidp,const pthread_attr_t *restrict_attr, void*(*start_rtn)(void*) ,void *restrict arg);

返回值

若成功则返回0 否则返回出错编号
返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,线程从创建开始后就开始执行。 该函数只有一个万能 指针 参数arg, 如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
看下面的示范代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef  struct Node
{
	int      data;
	char *   message;
}Node;

void InitNode (Node *s)
{
	s->data = 1;
	s->message = "taotao 2b";
}
void* thread(void *arg)
{
	while(1)
	{
		printf("message = %s ,  data = %d\n",((Node *)arg)->message,((Node *)arg)->data);
		
		sleep(2);
	}
	return NULL;
}

int main(void)
{
	Node p;
	InitNode(&p);
	
	pthread_t id;//线程标识符	
	int i,ret;
	ret = pthread_create(&id,NULL,thread,(void *)(&p));
	
	if(ret != 0)//执行不成功,成功的话函数会返回0 
	{
		printf("Create pthread error");
		exit(-1);
	}
	for(i = 0;i < 3; i++)
	{
		printf("this is the main process\n");
		sleep(1);
	}
		
	
	return 0;
}	
	
	

把传递的信息  放到一个地址中,在函数中对地址进行操作。运行结果:


linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
由 restrict 修饰的 指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的 指针表达式中。 由 restrict 修饰的 指针主要用于函数 形参,或指向由 malloc() 分配的内存空间。restrict  数据类型不改变程序的语义。  编译器能通过作出 restrict 修饰的 指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。

参数

第一个参数为指向线程 标识符指针
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。 (没有参数就直接不写,有参数的话就把参数放到一个结构中,然后结构地址传给第4个参数)
另外,在编译时注意加上-lpthread参数,以调用链接库。因为pthread并非Linux系统的默认库
Pthread_join
函数 pthread_join用来等待一个线程的结束。
头文件 : #include <pthread.h>
函数定义: int  pthread_join(pthread_t thread, void **retval);
描述 :
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。 并且thread指定的线程必须是joinable的。
参数 :
thread: 线程 标识符,即线程ID,标识唯一线程。
retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。 [1]
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef  struct Node
{
	int      data;
	char *   message;
}Node;

void InitNode (Node *s)
{
	s->data = 1;
	s->message = "taotao 2b";
}
void* thread(void *arg)
{
	while(1)
	{
		printf("message = %s ,  data = %d\n",((Node *)arg)->message,((Node *)arg)->data);
		
		sleep(2);
	}
	return NULL;
}

int main(void)
{
	Node p;
	InitNode(&p);
	
	pthread_t id;//线程标识符	
	int i,ret;
	ret = pthread_create(&id,NULL,thread,(void *)(&p));
	
	if(ret != 0)//执行不成功,成功的话函数会返回0 
	{
		printf("Create pthread error");
		exit(-1);
	}
	for(i = 0;i < 3; i++)
	{
		printf("this is the main process\n");
		sleep(1);
	}
	pthread_join(id,NULL);
	
	return 0;
}	
	
	
代码中如果没有 pthread_join 主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入 pthread_join后, 主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
所有线程都有一个线程号,也就是 Thread ID。其类型为 pthread_t。通过调用 pthread_self()函数可以获得自身的线程号。
运行结果:
因为我们线程调用的函数是写的一个死循环,不会结束,所以就不停的等待。看打印结果;

一个线程的结束有两种途径,一种是象我们上面的例子一样 ,函数结束了,调用它的线程也就结束了 ;另一种方式是通过函数pthread_exit来实现。它的函数原型为:

pthread_exit

void pthread_exit(void* retval);
一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则会返回错误代码。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *print_message_function( void *ptr )
{
	char *message;
	message = (char *) ptr;
	printf("%s \t", message);
	printf("PID: %ld \n", pthread_self());
	pthread_exit ("thread all done"); // 重点看 pthread_exit() 的参数,是一个字串,这个参数的指针可以通过
	// pthread_join( thread1, &pth_join_ret1);
}
int main()
{
	pthread_t thread1, thread2;
	char *message1 = "Thread 1";
	char *message2 = "Thread 2";
	int iret1, iret2;
	void *pth_join_ret1;//="thread1 has done!";
	void *pth_join_ret2;//="thread2 has done!";
	/* Create independant threads each of which will execute function */
	//pthread_create return 0 if create a thread is ok!
	iret1 = pthread_create( &thread1, NULL, print_message_function, (void*)"thread one_here");
	iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);
	
	pthread_join( thread1, &pth_join_ret1);
	pthread_join( thread2, &pth_join_ret2);
	if(pth_join_ret1==NULL || pth_join_ret2==NULL)
	{
	 printf("in %d lines \n",__LINE__);
	}
	
	printf("Thread 1 returns: %d\n",iret1);
	printf("Thread 2 returns: %d\n",iret2);
	printf("pthread_join 1 returns: %s\n",(char *)pth_join_ret1);
	printf("pthread_join 2 returns: %s\n",(char *)pth_join_ret2);
	exit(0);
}



可以看到pthread_exit的参数和 pthread_join 的参数是一个。
修改线程属性:
我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
  属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
  关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
  设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值