主线程和子线程的同步控制

Posted wzjhoutai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了主线程和子线程的同步控制相关的知识,希望对你有一定的参考价值。

一个线程的结束有两种途径,一种是象我们以下的样例一样。函数结束了。调用它的线程也就结束了。还有一种方式是通过函数pthread_exit来实现。另外须要说明的是,一个线程不能被多个线程等待,也就是说对一个线程仅仅能调用一次pthread_join。否则仅仅有一个能正确返回。其它的将返回ESRCH 错误。
在Linux中,默认情况下是在一个线程被创建后。必须使用此函数对创建的线程进行资源回收,可是能够设置Threads attributes来设置当一个线程结束时。直接回收此线程所占用的系统资源。具体资料查看Threads attributes。

范例:
//signaltest.c
  // 子线程堵塞,等待信号,然后输出字符串
  // 主线程从键盘录入字符。给子线程发信号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include<time.h>
pthread_ttid; sigset_tset;
voidmyfunc()
{
 printf("hello\n");
}
staticvoid*mythread(void*p)
{
 intsignum;
 while(1){
 sigwait(&set,&signum);
 if(SIGUSR1==signum)
 myfunc();
 if(SIGUSR2==signum)
 {
 printf("Iwillsleep2secondandexit\n");
 sleep(2);
 break;
 }
}
}
intmain()
{
chartmp;
void*status;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
sigaddset(&set,SIGUSR2);
sigprocmask(SIG_SETMASK,&set,NULL);
pthread_create(&tid,NULL,mythread,NULL);
while(1)
{
printf(":");
scanf("%c",&tmp);
if(‘a‘==tmp)
{
pthread_kill(tid,SIGUSR1);//发送SIGUSR1,打印字符串。

}
elseif(‘q‘==tmp)
{
//发出SIGUSR2信号。让线程退出,假设发送SIGKILL。线程将直接退出。

pthread_kill(tid,SIGUSR2);
//等待线程tid运行完成,这里堵塞。
pthread_join(tid,&status);
printf("finish\n");
break;
}
else
continue;
}
return0;
}
执行结果:
// 假设输入a。子线程打印"hello",主程序继续等待输入;
// 假设输入q,主程序等待子程序结束。子线程打印"I will sleep 2 second and exit",并延时两秒后结束。主线程随之打印"finish",程序结束。

在前面我们提到。能够通过pthread_join()函数来使主线程堵塞等待其它线程退出。这样主线程能够清理其它线程的环境。可是另一些线程,更喜欢自己来清理退出的状态,他们也不愿意主线程调用pthread_join来等待他们。我们将这一类线程的属性称为detached。假设我们在调用pthread_create()函数的时候将属性设置为NULL。则表明我们希望所创建的线程採用默认的属性,也就是joinable。假设须要将属性设置为detached,则參考以下的样例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void*start_run(void*arg)
{
//dosomework
}
 
intmain()
{
pthread_tthread_id;
pthread_attr_tattr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(&thread_id,&attr,start_run,NULL);
pthread_attr_destroy(&attr);
sleep(5);
exit(0);
}
在线程设置为joinable后,能够调用pthread_detach()使之成为detached。可是相反的操作则不能够。


  有。假设线程已经调用pthread_join()后。则再调用pthread_detach()则不会有不论什么效果。

相互排斥量是一种特殊的变量,它能够处于锁定状态(locked),也能够处于解锁状态(unlocked)。

假设相互排斥量是锁定的,那么必定有一个线程持有或拥有这个相互排斥量。假设没有不论什么一个线程持有这个相互排斥量,那么这个相互排斥量就处于解锁、空暇或可用状态(这三种状态有差别?!)。

当相互排斥量空暇,而且有一个线程试图获取这个相互排斥量时。这个线程就能够获得这个相互排斥量而不会被堵塞。假设相互排斥量处于锁定状态。那么试图获取这个相互排斥量的线程将被堵塞。并增加到这个相互排斥量的等待队列中。等待队列中的线程获得相互排斥量的顺序由系统决定。这种机制攻克了共享资源的相互排斥(Mutual Exclusive)訪问问题。

创建并初始化一个相互排斥量

POSIX使用pthread_mutex_t类型的变量来表示相互排斥量。

程序在使用pthread_mutex_t变量之前,必须对其进行初始化。

对于静态分配的pthread_mutex_t变量。仅仅要将PTHREAD_MUTEX_INITIALIZER赋给这个变量就可以,如:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

对于动态创建或不使用默认属性的相互排斥量来说,就要调用pthread_mutex_init函数来对其进行初始化,此函数的形式例如以下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

静态初始化通常比pthread_mutex_init更有效,并且能够在定义为全局变量时即完毕初始化。这样能够保证在不论什么线程開始运行之前。初始化即已完毕。

销毁一个相互排斥量

当不再使用应经定义了的相互排斥量时,须要将相互排斥量销毁。函数pthread_mutex_destroy用于销毁相互排斥量。它的形式为:

int pthread_mutex_destory(pthread_mutex_t *mutex);

參数mutex指向要销毁的相互排斥量。假设成功。函数返回0...

能够用pthread_mutex_init()又一次初始化被销毁的相互排斥量。

对相互排斥量的锁定和解锁

POSIX中有两个能够用来获取相互排斥量的函数。pthread_mutex_lock()和pthread_mutex_trylock()。pthread_mutex_lock()函数会使调用这个函数的线程移植堵塞到相互排斥量可用为止,而pthread_mutex_trylock()会马上返回,假设相互排斥量空暇,那么调用这个函数的线程将获得相互排斥量,否则返回EBUSY。

pthread_mutex_unlock()用来释放相互排斥量。

这三个函数的形式例如以下:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

若成功。都是返回0。

相互排斥量应用实例

因为在大多数机器中,对变量的增量和减量操作都不是原子的。比方通常情况下。增量操作包含三个步骤:将内存中数值装载到CPU寄存器中,将寄存器的值加1,将寄存器中的值写回内存。而机器并不能保证这三步之间不会发生调度,这就导致对变量的增量操作和减量操作有可能得不到期望的结果...

<span style="font-size:18px;">int increase(int *integer)
{
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int error;
if (error = pthread_mutex_lock(&lock);
return error;
*integer++;
return pthread_mutex_unlock(&lock);
}
int decrease(int *integer)
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int error;
if (error = pthread_mutex_lock(&lock);
return error;
*integer--;
return pthread_mutex_unlock(&lock);
}</span>
对于相互排斥量,我有一些体会:

体会1:相互排斥量仅仅是一种手段。怎样理解呢?它是用来保护某个共享资源的,对相互排斥量的使用重在“自觉”,比方有三个线程one、two、three,二者都须要处理一个全局变量share。共享一个相互排斥变量mutex。程序猿须要做的是,无论在哪里?无论对于哪个线程。在写变量share时。都要获取(调用pthread_mutex_lock函数)相互排斥量mutex,然后才開始写,写完之后再释放(pthread_mutex_unlock函数)mutex,如此这般,才干保证share仅仅被一个人写。但若有些线程“不听话”,比方线程three,它不这样做,不进行神马pthread_mutex_lock()、pthread_mutex_unlock()操作,它也能操作share,可是这样就违背了程序设计的初衷。

相互排斥量仅仅是一种手段,使用全屏自觉。

体会2:体会1中讲到的是对share的“写操作”进行锁定。但这不是绝对的,非常多时候要是详细情况而定,有时要求“读”“写”都锁定,有时仅仅要求“写”锁定...


条件变量。先谈谈自己的理解吧!

相互排斥量攻克了不同线程处理共享资源的问题。比方有俩线程one和two以及和一个共享资源share,one和two每次处理share的前提都是成功获取相互排斥量mutex。这种做法保证了共享资源在一段时间里仅仅被一个线程处理,也即保证了处理共享资源的原子性。

但这还不够。经常会有这种应用需求(mark nb),线程one处理完共享资源share之后,须要通知处于堵塞状态的two线程来接着进行处理。

单是相互排斥量可以解决这种问题吗?

显然不行。单是使用相互排斥量,若不计one线程和two线程处理程序的复杂性。那么one线程和two线程处理share的机会是均等的。可是。mark nb这种应用需求下,明显one线程对共享资源share的占用更有优势。

举例。one线程获取共享资源share,two线程把共享资源share处理成share_after:

<span style="font-size:18px;">one thread:
  while (1) {
  if (ok) {
  pthread_mutex_lock(&mutex);
  ...// got share
  pthread_mutex_unlock(&mutex);
  }
  }
two thread:
  while (1) {
  pthread_mutex_lock(&mutex);
  ...// let share -> share_after
  pthread_mutex_unlock(&mutex);
  }</span>
这样的仅仅是使用到了相互排斥量的程序设计合格吗?

显然不合格!

这样会造成这种问题产生。线程one还没成功获取到新的share。线程two就已经有机会对“老的”share进行处理了。而其实。我们须要的one线程每“if (ok)”一次。two才有机会运行一次。

怎样能满足这种需求呢?

条件变量!

关于对条件变量的解释。有文献曰:条件变量是用来通知共享数据的状态信息的机制。

在这种机制下,可以实现这种功能:线程two平时都是处于堵塞状态,直到线程one在if(ok)中激活它,使它有机会运行一次。下一个while循环又又一次处于堵塞状态,等待线程one下一次的“召唤”。

来看看怎样用代码完毕这种机制吧!

值得一提的是。因为涉及共享数据,因此条件变量是结合相互排斥量来使用的。

创建和销毁条件变量

POSIX用pthread_cond_t类型的变量来表示条件变量。程序必须在使用pthread_cond_t变量之前对其进行初始化。

对于那些静态分配的、使用默认属性的pthread_cond_t变量来说。能够直接将PTHREAD_COND_INITIALIZER赋给变量就能够完毕初始化。例如以下:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

对那些动态分配的或不使用默认属性的变量来说。就要调用pthread_cond_init函数来进行初始化。

该函数的形式为:

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 

參数attr是一个条件变量属性对象,假设将NULL传递给attr,则初始化一个具有默认属性的条件变量,否则,就要用与线程属性对象类似的方式,先创建一个条件变量属性对象。再设置它。

函数pthread_cond_destory销毁一个条件变量,该函数的形式为:

int pthread_cond_destory(pthread_cond_t *cond);

临时就介绍到这里吧,说得再多还不如看看代码演示样例。

基于《4.Linux C多线程的运行顺序问题》进行改动。如今要做这么一件事情,线程thread_one和线程thread_two共同处理一个全局变量i,thread_one:++i和打印i,thread_two处理的事情是:打印i。

thread_one和thread_two都处理打印i的事务,所不同的是,当i为3的倍数的时候。由thread_two打印,否则由thread_one打印。

程序例如以下:

<span style="font-size:18px;">#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static void thread_one(char* msg);
static void thread_two(char* msg);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int i = 1;
int temp;
int main(int argc, char** argv)
{
	pthread_t th_one, th_two;
	char * msg = "thread";
	printf("thread_one starting\n");
	if (pthread_create(&th_one, NULL, (void*)&thread_one, msg) != 0) {
		exit(EXIT_FAILURE);
	}
	printf("thread_two starting\n");
	if (pthread_create(&th_two, NULL, (void*)&thread_two, msg) != 0) {
		exit(EXIT_FAILURE);
	}
	pthread_join(th_one, NULL);
	pthread_join(th_two, NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	printf("Main thread is going over!\n");
	return 0;
}
static void thread_one(char* msg)
{
	int j = 0;
	while (i < 10) {
		pthread_mutex_lock(&mutex);
		if (i % 3 == 0) {
			temp = i;
			i++;
			pthread_cond_signal(&cond);
			pthread_mutex_unlock(&mutex);
			usleep(10);		// 为了让thread_two有足够运行时间,10ms
		} else {
			printf("I am one. loop %d\n", i);
			i++;
			pthread_mutex_unlock(&mutex);
		}
	}
	printf("one is over!\n");
}
static void thread_two(char* msg)
{
	int j = 0;
	while (i < 10) {
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond, &mutex);
		pthread_mutex_unlock(&mutex);
		printf("I am two. loop %d\n", temp);
	}
	printf("two is over!\n");
}</span>
编译运行结果例如以下:

[email protected]:/opt/pc_test/multithreading/t# ./main 

thread_one starting

thread_two starting

I am one. loop 1

I am one. loop 2

I am two. loop 3

I am one. loop 4

I am one. loop 5

I am two. loop 6

I am one. loop 7

I am one. loop 8

I am two. loop 9

two is over!

one is over!

Main thread is going over!

先来分析“pthread_cond_wait(&cond, &mutex);”

能够说是pthread_cond_wait()是条件变量机制中的最重要也是最难分析的函数。

使用条件变量机制直观上比較简单:调用pthread_cond_wait()的线程把自个儿给堵塞起来。然后等待别的线程调用pthread_cond_signal()把它唤醒。

pthread_cond_wait(&cond,&mutex)操作有两步,是原子操作:第一步是解锁,先解除之前的pthread_mutex_lock()锁定的mutex。第二步是挂起,堵塞并在等待对列里休眠,即线程thread_two挂起。直到被线程thread_one再次被唤醒,唤醒的条件是由“pthread_cond_signal(&cond);”发出的cond信号来唤醒。(这段话来自网络,总感觉有些问题。但我得明确pthread_mutex_lock()涉及两次mutex操作:上锁和解锁)

值得注意的是,pthread_cond_wait函数的用法例如以下:

pthread_mutex_lock(&mutex); // step a

pthread_cond_wait(&cond, &mutex); // step b

pthread_mutex_unlock(&mutex); // step c

step a比較easy理解了,step c怎样理解呢?

这得深刻解剖pthread_cond_wait()了。先看线程thread_two是怎样进入堵塞状态的:

a.解锁(解除step a锁定的mutex。如此使得与之共享锁的线程譬如thread_one可以拥有该mutex)、等待(堵塞睡眠之类的,此堵塞发生在pthread_cond_wait函数内部)。

b....等待。直到pthread_cond_signal()函数唤醒之。

c.此时被唤醒的线程上下文仍然在pthread_cond_wait函数内部。此时加锁,使得mutex被线程thread_two又一次拥有。然后处理返回值之类的。

所以上面的step a、b、c能够解剖为例如以下这般:

a------- pthread_mutex_lock(&mutex);

b1------- pthread_mutex_unlock(&mutex);

睡眠............唤醒

b2------- pthread_mutex_lock(&mutex);

c------- pthread_mutex_unlock(&mutex);

理解“pthread_cond_wait(&cond, &mutex);”有两个关键:

1.pthread_cond_wait函数涉及两次mutex操作;

2.pthread_cond_wait函数并非1次原子操作,线程堵塞于此函数内部,相同线程也是在此函数内部被唤醒。

接着来分析“pthread_cond_signal(&cond);”

关于pthread_cond_signal(),网文曰:

“pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于堵塞等待状态的线程,使其脱离堵塞状态,继续运行。假设没有线程处在堵塞等待状态。pthread_cond_signal()也会成功返回。

“使用pthread_cond_signal函数不会有‘惊群现象’产生,他最多仅仅给一个线程发信号。假如有多个线程正在堵塞等待着这个条件变量的话,那么是依据各等待线程优先级的高低确定哪个线程接收到信号開始继续运行。假设各线程优先级同样。则依据等待时间的长短来确定哪个线程获得信号。但不管怎样一个pthread_cond_signal()最多发信号一次。

须要注意的是pthread_cond_signal函数不涉及mutex的操作。所以在thread_one里调用pthread_cond_signal函数,thread_two仅仅能说被唤醒,但此时mutex还未被释放掉。也即thread_two的程序还不能被运行,直到thread_one释放掉mutex为止。

上文已经提到,对于pthread_cond_wait()的用法,大概例如以下:

pthread_mutex_lock(&mutex); // step a

pthread_cond_wait(&cond, &mutex); // step b

pthread_mutex_unlock(&mutex); // step c

其实,若须要在thread_two中处理共同资源,最好例如以下般这样:

pthread_mutex_lock(&mutex); // step a

pthread_cond_wait(&cond, &mutex); // step b

...处理共享数据 // step c

pthread_mutex_unlock(&mutex); // step d

相同,对于pthread_cond_signal()的用法。也能够例如以下般这样:

pthread_mutex_lock(&mutex); // step a

pthread_cond_signal(&cond, &mutex); // step b

...处理共享数据 // step c

pthread_mutex_unlock(&mutex); // step d

本文程序并非一个多么好的条件变量的应用实例。但通过它足以体会条件变量的用法~


參考:

http://baike.baidu.com/link?url=_PeSXdcSw05Twq3JlP7ldzbifo6X8C6YaE7ki1yGNNssnFEW4YWvYeWj0n6uPOTx1sp4-N9EJok5NJFg9yWDQ_

http://baike.baidu.com/link?

url=k2z4MrF9QFSw1yK5EiyOWBtqQ5HQGjz8O_yqRQ7Hib9pBm64HwLZ-Zva4IBflqkiarHa9ykWB3nIrGzNUEFF0K

http://m.blog.csdn.net/blog/sadjason/9706353


以上是关于主线程和子线程的同步控制的主要内容,如果未能解决你的问题,请参考以下文章

Unity主线程和子线程跳转调用(2)

主线程和子线程的区别

android 主线程和子线程之间的消息传递

主线程和子线程执行顺序问题

关于面试中异步与延时 执行顺序的预期结果问题

Python_线程线程效率测试数据隔离测试主线程和子线程