Linux的POSIX线程

1 线程的概念

在一个程序中的多个执行路线叫线程,线程是进程内部的一个控制序列。
进程执行fork调用时,将创建出该进程的一份副本。这个新进程有自己的变量和PID,它的时间调度也是独立的,它的执行几乎完全独立于父进程。
当在进程中创建新线程时,新线程有自己的栈(局部变量),但它和它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。除局部变量外,其它所有变量都在一个进程中的所有线程之间共享,子线程不能获取主线程的局部变量,除非将它传递进去!!!!!!

用fork调用创建新进程的代价很高,我们一般使用线程来实现并行处理。线程之间的切换所需的代价也很小。
单核CPU中线程的同时执行只是一个幻觉。

2 多线程编程

用gcc编译链接时要加上声明D_REENTRANT宏,告诉编译器我们需要可重入功能。可重入代码可以被多次调用而可以正常工作,这些调用可以是来自不同线程,也可以是嵌套调用。
用gcc编译链接时要加上-pthread来链接线程库。

#include 
int pthread_create(pthread_t* thread,pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);
void pthread_exit(void* retval);
int pthread_join(pthread_t th,void** thread_return);
pthread_t pthread_self();
int pthread_detach(pthread_t thread);

pthread_create函数用来创建新线程,thread标识符来引用新线程,attr参数设置线程属性,一般默认为NULL,start_routine函数指针设置线程启动执行的函数,arg为传递给该函数的参数。start_routine函数可以传递一个任意类型的参数并返回一个任意类型的指针。创建成功时返回0,否则设置error。
pthread_exit函数终止线程的执行,并返回一个指向某个对象的指针,注意不能返回指向子线程局部变量的指针。
pthread_join函数使一个线程等待其他线程执行完毕,th参数指定将要等待的线程,thread_return指针为指向子线程返回值的指针。成功时返回0,否则设置error。
pthread_self函数返回该线程的PID。
pthread_detach函数将PID线程设置为unjoinable状态。

pthread有两种状态joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。

#include 
#include 
#include 
#include 
#include 
char message[10] = "Hello world";//它是全局变量,主线程和子线程共享这个变量
void* thread_function(void* arg)
{
	printf("thread function is running,argument is %s\n",(char*)arg);
	sleep(3);//使子线程在此停顿,防止对全局变量的访问冲突,在后面通过信号量机制解决
	strcpy(message,"Bye!");
	pthread_exit("thread_function over\n");
}	

int main()
{
	pthread_t a_thread;
	void* thread_result;
	int res = pthread_create(&a_thread,NULL,thread_function,(void*)message);
	if(res != 0)
	{
		perror("thread create failed");
		exit(EXIT_FAILURE);
	}
	printf("main thread waiting for thread to finish\n");
	res = pthread_join(a_thread,&thread_result);
	if(res != 0)
	{
		perror("thread join failed");
		exit(EXIT_FAILURE);
	}
	printf("thread has joined,it returned %s\n",(char*)thread_result);
	printf("message now is %s\n",message);
	exit(EXIT_SUCCESS)}

Linux的POSIX线程_第1张图片
使用轮询技术的多线程,我们接下来验证两个线程是同时执行的,在单核CPU上线程的同时执行实际上靠CPU在线程之间快速切换实现的。注意这个程序的子线程没有用pthread_exit函数,子线程执行完毕后默认就返回。同时没有使用信号量机制,可能会造成内存访问冲突。

#include 
#include 
#include 
#include 

char message[10] = "Hello world";//它是全局变量,主线程和子线程共享这个变量
int run_now = 1;
void* thread_function(void* arg)
{
	int print_count2 = 0;
	while(print_count2++ < 20)
	{
		if(run_now == 2)
		{
			printf("2\n");
			run_now = 1;
		}
		else
			sleep(1);
	}
	sleep(3);
}	

int main()
{
	pthread_t a_thread;
	void* thread_result;
	int print_count1 = 0;
	int res = pthread_create(&a_thread,NULL,thread_function,(void*)message);
	if(res != 0)
	{
		perror("thread create failed");
		exit(EXIT_FAILURE);
	}
	
	while(print_count1++ < 20)
	{
		if(run_now == 1)
		{
			printf("1\n");
			run_now = 2;
		}
		else
			sleep(1);
	}
	
	printf("main thread waiting for thread to finish\n");
	res = pthread_join(a_thread,&thread_result);
	if(res != 0)
	{
		perror("thread join failed");
		exit(EXIT_FAILURE);
	}
	printf("thread joined");
	exit(EXIT_SUCCESS)}

Linux的POSIX线程_第2张图片

3 线程间的同步

有两种方机制为我们提供了更好地控制线程执行和访问代码临界区域的接口函数,它们分别是信号量和互斥量。

3.1 用信号量同步

信号量有两组接口,一组是POSIX的实时拓展,用于线程同步,而另一组被称为系统V信号量,用于进程同步。
信号量是一个特殊类型的变量,它可以被增加或减少,但对其的访问被保证是原子操作,一次只有一个线程能访问该变量,系统保证多线程对它的访问不会是同时的。
信号量有两种,二进制信号量取值仅有0和1,计数信号量有更大的取值范围。

#include 
int sem_init(sem_t* sem,int pshared,unsigned int val);
int sem_wait(sem_t* sem);
int sem_trywait(sem_t* sem);
int sem_post(sem_t* sem);
int sem_destroy(sem_t* sem);

这些函数成功时返回0,失败时设置errno。
sem_init函数初始化sem指向的信号量对象,pshared控制信号量的类型,为0时表示这个信号量是当前进程的局部信号量,否则该信号量可以在多个进程间共享,暂不支持!val为信号量的初始值。
sem_post函数以原子操作的方式给信号量的值加1(原子操作指如果两个线程企图同时给一个信号量加1,它们不会互相干扰)。
sem_wait函数以原子操作的方式将信号量的值减1,但它会阻塞直到信号量有非零值时才开始减法操作。sem_trywait是非阻塞版本。
sem_destroy函数清理该信号量所拥有的资源,如果该信号量被其它线程等待,则失败。

#include 
#include 
#include 
#include 
#include 

void* thread_function(void* arg);
sem_t bin_sem;
pthread_t a_thread;
#define WORK_SIZE 1024
char buff[WORK_SIZE];

int main()
{
	int res = sem_init(&bin_sem,0,0);
	if(res != 0)
	{
		perror("sem init failed");
		exit(EXIT_FAILURE);
	}

	int ret = pthread_create(&a_thread,NULL,thread_function,NULL);
	if(res != 0)
	{
		perror("thread creat failed");
		exit(EXIT_FAILURE);
	}

	printf("input some text,enter 'end' to finish:");
	fgets(buff,WORK_SIZE,stdin);
	while(strncmp("end",buff,3) != 0)
	{
		sem_post(&bin_sem);
		printf("input some text,enter 'end' to finish:");
		fgets(buff,WORK_SIZE,stdin);
	}

void* thread_function(void* arg)
{
	sem_wait(&bin_sem);
	while(strncmp("end",buff,3) != 0)
	{
		printf("you input %d char\n",strlen(buff) - 1);
		sem_wait(&bin_sem);
	}
	pthreat_exit(NULL);
}

3.2 用互斥量同步

互斥量的使用比信号量要简单,它允许程序员锁住某个对象,使得每次只有一个线程能访问它。为了控制对关键代码段的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁它。

#include 
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
int pthread_mutex_destroy(pthread_mutex_t* mutex);

这些函数成功时返回0,失败时返回错误代码,但不设置errno。mutexattr参数指定互斥量的属性,默认为fast。此时,如果我们对一个已经加锁的互斥量调用pthread_mutex_lock,那么程序会被阻塞,由于拥有互斥量的这个线程正是现在被阻塞的线程,所有互斥量就永远也不会被解锁了,程序进入死锁状态。

4 线程属性

脱离

脱离线程就是unjoinable状态的线程,它不再和主线程合并,设置脱离状态可以允许第二个线程独立地完成工作,而无需原先的线程等待它。我们可以通过修改线程属性或调用pthread_detach的方法来创建。

#include 
int pthread_attr_init(pthread_attr_t* attr);
int pthread_attr_destory(pthread_attr_t* attr);
int pthread_attr_setdetachstate(pthread_attr_t* attr,int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t* attr,int* detachstate);

pthread_attr_initpthread_attr_destory用于初始化和删除线程属性对象。
detachedstate属性有两个标志,PTHREAD_CREATE_JOINABLE和PTHREAD_CREATE_DETACHED,默认为PTHREAD_CREATE_JOINABLE。

5 取消一个线程

线程可以在被要求终止时改变其行为,一个线程可以要求另一个线程终止,就像发送信号一样。

#include 
int pthread_cancel(pthread_t thread);
int pthread_setcancelstate(int state,int* oldstate);
int pthread_setcanceltype(int type,int* oldstate);

一个线程调用pthread_cancel函数取消线程thread。
接收方调用pthread_setcancelstate函数设置自己的取消状态,state可取值PTHREAD_CANCEL_ENABLE允许线程接收取消请求,PTHREAD_CANCEL_DISABLE忽略取消请求。oldstate用于获取先前状态。state默认为PTHREAD_CANCEL_ENABLE。
当线程被设置为PTHREAD_CANCEL_ENABLE时,用pthread_setcanceltype设置取消类型。type有两种取值,PTHREAD_CANCEL_ASYNCHRONOUS使线程在接收到取消请求后立即取消,PTHREAD_CANCEL_DEFERRED使被取消线程在发出请求的线程执行了下述函数之一后才取消。它们有pthread_join、pthread_cond_wait、sem_wait等等。type默认为PTHREAD_CANCEL_DEFERRED。

#include 
#include 
#include 
#include 
void* thread_function(void* arg);
int main()
{
	pthread_t a_thread;
	pthread_create(&a_thread,NULL,thread_function,NULL);
	sleep(3);

	printf("cancel thread.....\n");
	pthread_cancel(a_thread);
	printf("Waiting for thread to finish\n");
	pthread_join(a_thread,NULL);

	exit(EXIT_SUCCESS);
}

void* thread_function(void* arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
	printf("thread function is running\n");
	for(int i = 0;i < 10;i++)
	{
		printf("thread is still alive %d\n",i);
		sleep(1);
	}
	pthread_exit(0);
}

Linux的POSIX线程_第3张图片

6 多个线程的程序

#include 
#include 
#include 
#include 
#include 
#define NUM_THREADS 6
void* thread_function(void* arg)
{
	int my_num = (int)arg;
	printf("thread_func is running,argument is %d\n",my_num);
	int rand_num = 1 + (int)(9.0 * rand() / (RAND_MAX + 1.0));
	sleep(rand_num);
	printf("bye from %d\n",my_num);
	pthread_exit(NULL);
}

int main()
{
	pthread_t a_thread[NUM_THREADS];
	void* thread_result;
	for(int lots_of_threads = 0;lots_of_threads < NUM_THREADS;lots_of_threads ++)
		pthread_create(&a_thread[lots_of_threads],NULL,thread_function,(void*)lots_of_threads );

	printf("waiting for threads to finish...\n");
	for(int lots_of_threads = NUM_THREADS -1;lots_of_threads >= 0;lots_of_threads++)
	{
		pthread_join(a_thread[lots_of_threads],NULL);
		printf("pick up a thread\n");
	printf("All done\n");
	exit(EXIT_SUCCESS);
}

你可能感兴趣的:(Linux的POSIX线程)